Í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, } }