Custom hooks en la práctica
El capítulo de Hooks introdujo la idea: un custom hook es una función que empieza con use y compone hooks en lógica reutilizable. Este capítulo es sobre cómo se ve eso en la práctica, y el curso lo demuestra de manera contundente: después de toda una sección construyendo un componente Toggle sin render, un custom hook lo reemplaza en una fracción del código, y el componente se elimina.
La línea que vale la pena trazar con precisión es contra una función utilitaria ordinaria. Una función que trae datos o formatea una fecha es una utilidad. Un custom hook llama otros hooks, por eso las reglas de hooks aplican dentro de él.
Extraer un efecto: useEffectOnUpdate
El componente Toggle terminó con un chunk de lógica cuyo trabajo era "ejecutar este callback cuando el estado cambia, pero nunca en el primer render": un useEffect protegido por una ref firstRender. Eso no es lógica de toggle; es lógica de efecto, útil en cualquier lado. Extraerla produce un hook con la misma firma que useEffect:
export default function useEffectOnUpdate(effectFunction, deps) {
const firstRender = useRef(true)
useEffect(() => {
if (firstRender.current) {
firstRender.current = false
} else {
effectFunction()
}
}, deps)
}Quienes lo usan escriben useEffectOnUpdate(onToggle, [on]) y la gestión del primer render desaparece de su código. Los hooks que envuelven otros hooks de esta manera vale la pena construirlos incluso cuando la lógica envuelta es pocas líneas, porque el nombre comunica la intención.
La protección tiene un detalle solo en desarrollo
El mismo que flagueó render props: StrictMode gasta la ref en su primera ejecución de efecto, así que el callback se dispara en la segunda. Los builds de producción no hacen esto, y la solución duradera es comparar el valor contra el anterior en lugar de rastrear si este es el primer render.
Extraer estado: useToggle
useEffectOnUpdate no retorna nada, como useEffect. La mayoría de los custom hooks devuelven algo, como useState lo hace, y la forma de lo que retornan es una decisión de diseño. useToggle reúne el booleano, la función flip y el callback de cambio en un solo 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 llamada a useToggle obtiene su propio useState, así que dos usuarios tienen dos booleanos independientes, ya sea que sean dos componentes o un componente llamando al hook dos veces.
Tres decisiones ahí, cada una un patrón que reutilizarás:
- Retorna un array cuando los valores son un par natural que el usuario renombrará, exactamente como
useState: un usuario destructura[on, toggle], otro[open, toggleOpen]. Retorna un objeto cuando hay varios valores y los nombres importan más que el orden. - Toma un objeto de configuración en lugar de parámetros posicionales. Con
useToggle(false, callback)un usuario que solo quiere el callback está forzado a suministrar el booleano primero; con{ initialValue, onToggle }pasan lo que significan y saltan el resto, y cada opción puede llevar un default. - Defaultea el callback a una noop (
() => {}) así el hook puede llamaronToggle()sin condiciones sin crashes en usuarios que nunca pasaron uno.
La recompensa: eliminar el componente
Con useToggle a la mano, el widget de estrella deja de necesitar completamente la maquinaria 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} />
}El menú mantiene su forma de componente compuesto, porque sus piezas aún necesitan estado implícito compartido, pero su proveedor ahora obtiene sus valores del hook: Menu llama useToggle, pasa { open, toggleOpen } a través de su propio context, y los archivos del componente Toggle sin render se eliminan.
Esa eliminación es la lección real de la sección. El componente sin render se ganó su lugar mientras muchos componentes compuestos podrían haberlo compartido; una vez que la lógica cabía en un hook, el hook era la herramienta más simple, y sacar código funcionando del que estabas orgulloso es una parte normal del trabajo.
Los dos patrones se complementan: un hook comparte lógica, un componente sin render comparte lógica a través del árbol vía context y composición. Llega al hook primero, y deja que un componente se gane la ceremonia extra. El curso cierra esta sección con un proyecto en solitario, Component Library++, que pone cada patrón de estos cinco capítulos en una librería.
use y que se construye sobre hooks como useState para empaquetar un pedazo de lógica. Una vez que useToggle existe, cualquier componente puede obtener un valor encendido-apagado y una función flip en una línea, de la misma forma que useState te entrega un valor y un setter. Cada componente que lo llama obtiene su propia copia del estado, así que un hook es una receta que reutilizas en lugar de un valor que todos comparten.
Siguiente: Routing, donde la URL empieza a decidir qué componentes se renderizan.

