Índice
- Retorne funções dos manipuladores
- Separe responsabilidades
- Use objetos map em vez de condições
- Coloque variáveis independentes fora do ciclo de vida do React
Retorne funções dos manipuladores
Se você está familiarizado com programação funcional, sabe do que estou falando. Podemos facilmente chamá-lo de "currying" de alguma forma. Em essência, definimos alguns parâmetros de função antecipadamente.
Temos um problema explícito com o código de boilerplate abaixo. Essa técnica vai nos ajudar!
export default function App() {
const [user, setUser] = useState({
name: "",
surname: "",
address: ""
});
// Primeiro handler
const handleNameChange = (e) => {
setUser((prev) => ({
...prev,
name: e.target.value
}));
};
// Segundo handler!
const handleSurnameChange = (e) => {
setUser((prev) => ({
...prev,
surname: e.target.value
}));
};
// Terceiro handler!!!
const handleAddressChange = (e) => {
setUser((prev) => ({
...prev,
address: e.target.value
}));
};
// E se tivéssemos mais inputs? Deveríamos criar outro handler pra ele?
return (
<>
<input value={user.name} onChange={handleNameChange} />
<input value={user.surname} onChange={handleSurnameChange} />
<input value={user.address} onChange={handleAddressChange} />
</>
);
}Solução
export default function App() {
const [user, setUser] = useState({
name: "",
surname: "",
address: ""
});
const handleInputChange = (field) => {
return (e) => {
setUser((prev) => ({
...prev,
[field]: e.target.value
}));
};
};
return (
<>
<input value={user.name} onChange={handleInputChange("name")} />
<input value={user.surname} onChange={handleInputChange("surname")} />
<input value={user.address} onChange={handleInputChange("address")} />
{JSON.stringify(user)}
</>
);
}Separe responsabilidades
Criar um componente "Deus" é um erro comum que os desenvolvedores cometem. É chamado de "Deus" porque contém muitas linhas de código difíceis de entender e manter. Eu recomendo fortemente dividir os componentes em conjuntos de submódulos independentes.
Uma estrutura típica para isso seria:
- Módulo UI, responsável apenas pela representação visual.
- Módulo de Lógica/Modelo, contendo apenas a lógica de negócios. Um exemplo disso é um hook personalizado.
- Módulo de Biblioteca, contendo todas as utilidades necessárias para o componente.
Aqui está um pequeno exemplo de demonstração para ajudar a ilustrar esse conceito.
export function ListComponent() {
// Nosso estado local
const [list, setList] = useState([]);
// Handler buscar os dados do server
const fetchList = async () => {
try {
const resp = await fetch("https://www.url.com/list");
const data = await resp.json();
setList(data);
} catch {
showAlert({ text: "Something went wrong!" });
}
};
// Fazemos a requisição apenas na montagem do componente
useEffect(() => {
fetchList();
}, []);
// Handler responsável por deletar itens
const handleDeleteItem = (id) => {
return () => {
try {
fetch(`https://www.url.com/list/${id}`, {
method: "DELETE"
});
setList((prev) => prev.filter((x) => x.id !== id));
} catch {
showAlert({ text: "Something went wrong!" });
}
};
};
// Renderizado os dados
return (
<div className="list-component">
{list.map(({ id, name }) => (
<div key={id} className="list-component__item>">
{/* We want to trim long name with ellipsis */}
{name.slice(0, 30) + (name.length > 30 ? "..." : "")}
<div onClick={handleDeleteItem(id)} className="list-component__icon">
<DeleteIcon />
</div>
</div>
))}
</div>
);
}Devemos começar escrevendo nossos utilitários que serão usados nos módulos de modelo e UI.
export async function getList(onSuccess) {
try {
const resp = await fetch("https://www.url.com/list");
const data = await resp.json();
onSuccess(data)
} catch {
showAlert({ text: "Something went wrong!" });
}
}
export async function deleteListItem(id, onSuccess) {
try {
fetch(`https://www.url.com/list/${id}`, {
method: "DELETE"
});
onSuccess()
} catch {
showAlert({ text: "Something went wrong!" });
}
}
export function trimName(name) {
return name.slice(0, 30) + (name.lenght > 30 ? '...' : '')
}Agora precisamos implementar nossa lógica de negócios. Será apenas um hook personalizado retornando as coisas que precisamos.
export function useList() {
const [list, setList] = useState([]);
const handleDeleteItem = useCallback((id) => {
return () => {
deleteListItem(id, () => {
setList((prev) => prev.filter((x) => x.id !== id));
})
};
}, []);
useEffect(() => {
getList(setList);
}, []);
return useMemo(
() => ({
list,
handleDeleteItem
}),
[list, handleDeleteItem]
);
}O último passo é escrever nosso módulo UI e, em seguida, combiná-lo com todos os outros módulos.
export function ListComponentItem({ name, onDelete }) {
return (
<div className="list-component__item>">
{trimName(name)}
<div onClick={onDelete} className="list-component__icon">
<DeleteIcon />
</div>
</div>
);
}
export function ListComponent() {
const { list, handleDeleteItem } = useList();
return (
<div className="list-component">
{list.map(({ id, name }) => (
<ListComponentItem
key={id}
name={name}
onDelete={handleDeleteItem(id)}
/>
))}
</div>
);
}Use objetos map em vez de condições
Se você precisa exibir vários elementos dependendo da variável, pode implementar essa dica. Usar essa estratégia simples torna os componentes mais declarativos e simplifica a compreensão do código. Além disso, torna mais fácil estender ainda mais a funcionalidade.
Problema
function Account({type}) {
let Component = UsualAccount
if (type === 'vip') {
Component = VipAccount
}
if (type === 'moderator') {
Component = ModeratorAccount
}
if (type === 'admin') {
Component = AdminAccount
}
return (
<div className='account'>
<Component />
<AccountStatistics />
</div>
)
}Solução
const ACCOUNTS_MAP = {
'vip': VipAccount,
'usual': UsualAccount,
'admin': AdminAccount,
'moderator': ModeratorAccount,
}
function Account({type}) {
const Component = ACCOUNTS_MAP[type]
return (
<div className='account'>
<Component />
<AccountStatistics />
</div>
)
}Coloque variáveis independentes fora do ciclo de vida do React
A ideia é separar a lógica que não requer métodos do ciclo de vida do componente React do próprio componente. Isso melhora a clareza do código tornando as dependências mais explícitas. Portanto, torna-se muito mais fácil ler e entender os componentes.
Problema
function useItemsList() {
const defaultItems = [1, 2, 3, 4, 5]
const [items, setItems] = useState(defaultItems)
const toggleArrayItem = (arr, val) => {
return arr.includes(val) ? arr.filter(el => el !== val) : [...arr, val];
}
const handleToggleItem = (num) => {
return () => {
setItems(toggleArrayItem(items, num))
}
}
return {
items,
handleToggleItem,
}
}Solução
const DEFAULT_ITEMS = [
1, 2, 3, 4, 5
]
const toggleArrayItem = (arr, val) => {
return arr.includes(val) ? arr.filter(el => el !== val) : [...arr, val];
}
function useItemsList() {
const [items, setItems] = useState(DEFAULT_ITEMS)
const handleToggleItem = (num) => {
return () => {
setItems(toggleArrayItem(items, num))
}
}
return {
items,
handleToggleItem,
}
}
