Mejores Prácticas de React Hooks
Un resumen condensado de las 25 mejores prácticas más importantes extraídas de cada página en esta sección.
Busca en todas las páginas de la documentación
Un resumen condensado de las 25 mejores prácticas más importantes extraídas de cada página en esta sección.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
use para que el plugin ESLint rules-of-hooks realmente ejecute las reglas; sin el prefijo, las llamadas condicionales ilegales siguen rompiendo React pero ningún error de linting las detecta.[value, setValue] as const proporciona a los consumidores readonly [T1, T2] con tipos de desestructuración correctos; sin as const, TypeScript amplía a (T1 | T2)[] y se pierde el orden de la tupla.useActionState desde "react" - no el deprecado useFormState desde "react-dom" - y recuerda que la nueva firma devuelve [state, formAction, isPending] con estado pending incluido.(prevState, formData) => newState reemplaza el estado completamente, así que dispersa ...prevState cuando solo quieras fusionar un campo; olvidar esto borra cada campo sin tocar en cada envío.useCallback es inútil por sí solo - previene re-renderizados solo cuando un hijo React.memo o un efecto/memo de dependencia realmente compara identidad, así que no lo rocíes en cada manejador.dispatch de useReducer es permanentemente estable en los renders, así que pasarlo hacia abajo es a menudo más limpio que envolver manejadores en useCallback en cada nivel; lo mismo aplica a los setters de estado.<Context value={{…}}> re-renderiza cada consumidor en cada render del padre; envuelve el valor en useMemo - <Ctx value={useMemo(() => ({ theme, toggle }), [theme, toggle])}> - o divide el estado y el actualizador en contextos separados para detener la avalancha.createContext(default) se usa solo cuando ningún Provider envuelve el árbol, no como el valor inicial del Provider; confía en esto para protegerte contra proveedores faltantes con un hook personalizado que lance si el contexto es null.useDeferredValue aplaza el renderizado de consumidores, así que el hijo pesado debe estar envuelto en React.memo o el padre lo re-renderiza con el valor actual de todas formas, haciendo que el hook sea un no-op.AbortController y aborta en la limpieza para descartar resultados obsoletos: useEffect(() => { const ac = new AbortController(); fetch(url, { signal: ac.signal }); return () => ac.abort(); }, [url]).useEffect(() => { async function load() { await fetch(url) } load(); }, [url]) - React advierte sobre este patrón porque la Promise devuelta nunca se espera para la limpieza.useId devuelve un identificador estable SSR por posición de árbol - perfecto para htmlFor, aria-describedby y cableado de formularios - pero nunca para keys de lista (un ID por llamada) y nunca dentro de bucles o condicionales.useId colisionantes por defecto; pasa identifierPrefix a cada createRoot/hydrateRoot para que micro-frontends o widgets incrustados se mantengan distintos.useMemo para corrección - úsalo para rendimiento y estabilidad de referencia, no semántica de programa.localStorage dentro de useMemo rompe el modelo de renderizado de React y se comporta mal bajo Strict Mode y renderizado concurrente.useOptimistic desaparece silenciosamente a menos que addOptimistic se ejecute dentro de una acción de formulario, manejador de Server Action o startTransition explícito; la superposición solo persiste mientras una transición está pendiente, con reversión automática en error.return { ...state, count: state.count + 1 } no state.count++; return state - nunca busques ni escribas en almacenamiento, e incluye siempre una rama default: return state o acciones desconocidas limpian el estado a undefined.useState(() => computeInitial()) y useReducer(reducer, arg, init) para que el inicializador se ejecute solo al montar; useState(computeInitial()) ejecuta el cálculo en cada renderizado y desecha el resultado..current puede reflejar un paso de renderizado diferente del que se pinta, así que lee refs dentro de efectos, manejadores de eventos o layout effects - no inline en el cuerpo del componente.useRef adjunto a un nodo del DOM es null durante el primer renderizado, así que intentar tocar ref.current.focus() inline lanza un error; hazlo en useEffect, useLayoutEffect o un manejador, y recuerda que el ref de un elemento renderizado condicionalmente es null mientras está oculto.setCount(count + 1) en el mismo manejador se incrementan solo una vez porque todas se cierran sobre el mismo count obsoleto; la forma del actualizador setCount(prev => prev + 1) se compone correctamente bajo el batching automático de React 18+.useState(() => parseJSON(localStorage.x)) ejecuta el análisis solo al montar; useState(parseJSON(localStorage.x)) lo ejecuta en cada renderizado y descarta todo excepto el primero - que es costoso y puede lanzar durante SSR.startTransition solo aplaza setters de React state - refs, almacenes externos y escrituras DOM síncronas no se ven afectadas; también recuerda que React 18 requiere una callback síncrona mientras React 19 permite callbacks async nativamente.use(promise) trata un objeto promise fresco como una nueva solicitud pendiente, así que crear una promise inline en el renderizado causa suspensión infinita; la promise debe venir de props, un padre, un caché o un ref estable - y siempre sentarse bajo una Suspense más límite de error.Revisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥