Hooks personalizados na prática
O capítulo Hooks apresentou a ideia: um hook personalizado é uma função que começa com use e compõe hooks em lógica reutilizável. Este capítulo mostra como isso funciona na prática, e o curso faz o ponto de forma dramática: depois de toda uma seção construindo um componente Toggle sem renderização, um hook personalizado o substitui em uma fração do código, e o componente é deletado.
A linha que vale a pena desenhar com precisão é contra uma função utilitária comum. Uma função que busca dados ou formata uma data é uma utilidade. Um hook personalizado chama outros hooks, é por isso que as regras de hooks se aplicam dentro dele.
Extraindo um efeito: useEffectOnUpdate
O componente Toggle acabou com um pedaço de lógica cuja função era "executar este callback quando o estado muda, mas nunca na primeira renderização": um useEffect protegido por uma ref firstRender. Isso não é lógica de toggle; é lógica de efeito, útil em qualquer lugar. Extrair isso produz um hook com a mesma assinatura do próprio useEffect:
export default function useEffectOnUpdate(effectFunction, deps) {
const firstRender = useRef(true)
useEffect(() => {
if (firstRender.current) {
firstRender.current = false
} else {
effectFunction()
}
}, deps)
}Quem chama escreve useEffectOnUpdate(onToggle, [on]) e a contabilidade da primeira renderização desaparece do seu código. Hooks que envolvem outros hooks assim às vezes valem a pena construir mesmo quando a lógica envolvida tem apenas algumas linhas, porque o nome comunica a intenção.
A guarda tem uma complicação só em desenvolvimento
A mesma que render props sinalizou: StrictMode consome a ref na primeira execução do efeito, então o callback dispara na segunda. Builds de produção não fazem isso, e a correção durável é comparar o valor contra o anterior em vez de rastrear se esta é a primeira renderização.
Extraindo estado: useToggle
useEffectOnUpdate não retorna nada, como useEffect. A maioria dos hooks personalizados devolve algo, como useState faz, e a forma do que retornam é uma decisão de design. useToggle reúne o booleano, a função de alternância e o callback de mudança em um só lugar:
export default function useToggle({ initialValue = false, onToggle = () => {} } = {}) {
const [on, setOn] = useState(initialValue)
const toggle = () => setOn(prevOn => !prevOn)
useEffectOnUpdate(onToggle, [on])
return [on, toggle]
}const [on, toggle] = useToggle({ initialValue: true })Cada chamada a useToggle recebe seu próprio useState, então dois chamadores mantêm dois booleanos independentes, sejam dois componentes ou um componente chamando o hook duas vezes.
Três decisões ali, cada uma um padrão que você reutilizará:
- Retorne um array quando os valores são um par natural que o chamador vai renomear, exatamente como
useState: um chamador desestrutura[on, toggle], outro[open, toggleOpen]. Retorne um objeto quando há vários valores e os nomes importam mais que a ordem. - Receba um objeto de configuração em vez de parâmetros posicionais. Com
useToggle(false, callback)um chamador que só quer o callback é obrigado a fornecer o booleano primeiro; com{ initialValue, onToggle }ele passa o que quer dizer e pula o resto, e toda opção pode vir com um padrão. - Estabeleça o callback como noop padrão (
() => {}) para que o hook possa chamaronToggle()incondicionalmente sem quebrar chamadores que nunca passaram um.
O resultado: deletando o componente
Com useToggle em mãos, o widget de estrela para de precisar de qualquer maquinaria de Toggle:
export default function Star({ onChange }) {
const [on, toggle] = useToggle({ onToggle: onChange })
return on
? <BsStarFill className="star filled" onClick={toggle} />
: <BsStar className="star" onClick={toggle} />
}O menu mantém sua forma de componente composto, porque suas peças ainda precisam de estado implícito compartilhado, mas seu provedor agora pega seus valores do hook: Menu chama useToggle, passa { open, toggleOpen } pelo seu próprio contexto, e os arquivos do componente Toggle sem renderização são deletados.
Esse apagamento é a verdadeira lição da seção. O componente sem renderização se justificou enquanto muitos componentes compostos poderiam compartilhá-lo; uma vez que a lógica cabia em um hook, o hook era a ferramenta mais simples, e remover código funcionando do qual você se orgulhava é uma parte normal do trabalho.
Os dois padrões se complementam: um hook compartilha lógica, um componente sem renderização compartilha lógica pela árvore via contexto e composição. Comece com o hook, e deixe um componente ganhar a cerimônia extra. O curso encerra essa seção com um projeto solo, Component Library++, que coloca todo padrão desses cinco capítulos em uma biblioteca.
use e que se constrói sobre hooks como useState para empacotar um pedaço de lógica. Uma vez que useToggle existe, qualquer componente consegue um valor ligado-desligado e uma função de alternância em uma linha, do mesmo jeito que useState te entrega um valor e um setter. Cada componente que o chama recebe sua própria cópia do estado, então um hook é uma receita que você reutiliza em vez de um valor que todos compartilham.
Próximo: Roteamento, onde a URL começa a decidir quais componentes renderizam.

