Las reglas de los hooks son dos: llama a los hooks solo en el nivel superior de tu componente, nunca dentro de una condición, un bucle, una función anidada o después de un return temprano; y llámalos solo desde componentes de función o desde hooks personalizados. Existen porque React distingue los hooks por el orden en que los llamas, así que ese orden debe ser el mismo en cada renderizado.
React guarda los valores de los hooks de un componente en una lista unida a ese componente. En cada renderizado recorre la lista: la primera llamada a un hook recibe el primer lugar, la segunda el segundo lugar, y así sucesivamente. No hay nombres, solo posiciones. El ejemplo de abajo es una versión de juguete de esa idea en unas pocas líneas de JavaScript puro, no el código propio de React, para que puedas romperlo sin riesgo.
El primer renderizado llena tres lugares: Ada, Countess, 36. Haz clic en "Render without nickname": la llamada condicional se salta, así que la llamada de age ocupa el lugar 1 y lee Countess. El React real nota el desajuste en algunos casos y lanza un error, y en otros entrega el valor equivocado al hook equivocado, que es peor. Mueve la llamada toyUseState('Countess') por encima del if y ambos botones dan la edad correcta.
Las dos reglas en código
function Profile({ user }) {
// Good: always called, always in this order
const [tab, setTab] = useState('posts');
const theme = useContext(ThemeContext);
useEffect(() => {
document.title = user.name;
}, [user.name]);
// Bad: inside a condition, a loop or a nested function
if (user.isAdmin) {
const [log, setLog] = useState([]); // breaks the order
}
}
Una llamada a un hook que siempre se alcanza, en el mismo orden, está bien en cualquier parte del cuerpo, siempre que vaya antes de cualquier return temprano.
Arreglar un hook llamado de forma condicional
El linter lo reporta como React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. La solución siempre es la misma: llama al hook cada vez y pon la condición dentro de él.
// Broken: the effect exists on some renders and not others
function Notifications({ user }) {
if (user) {
useEffect(() => {
console.log('subscribe for', user.name);
}, [user]);
}
return null;
}
El efecto se declara en cada renderizado, y el if de dentro decide si hace algo. Inicia y cierra sesión varias veces y la consola muestra una suscripción por cada inicio de sesión y una cancelación por cada cierre.
Hooks después de un return temprano
Un return temprano también es una condición. Esto produce "Rendered more hooks than during the previous render" la primera vez que loading pasa a false, porque ese renderizado alcanza un hook que el anterior nunca llamó. Cuando un renderizado llama a menos hooks que el anterior, el mensaje es "Rendered fewer hooks than expected".
// Broken: useState is skipped while loading
function Comments({ loading, comments }) {
if (loading) return <p>Loading...</p>;
const [expanded, setExpanded] = useState(false);
return <List items={comments} expanded={expanded} />;
}
Todos los hooks están por encima del return, así que ambas ramas llaman a los mismos hooks. Haz clic en "Toggle loading", expande la lista y luego alterna la carga dos veces: la lista recuerda que estaba expandida, porque el estado nunca se saltó.
Hooks en un bucle
No puedes llamar a useState una vez por elemento en un bucle, porque el número de elementos cambia. Dale a cada elemento su propio componente y llama al hook ahí; cada instancia tiene sus propios lugares.
"Invalid hook call"
El mensaje empieza con "Invalid hook call. Hooks can only be called inside of the body of a function component." Tiene tres causas habituales:
- El hook se ejecuta mientras React no está renderizando un componente de función. En un manejador de eventos, dentro del callback de un efecto, en un componente de clase o a nivel de módulo. Muévelo al cuerpo del componente o a un hook personalizado, y llama al hook personalizado desde el componente.
- Las versiones de
reactyreact-domno coinciden. Instala la misma versión de ambos. - Hay dos copias de React. Un paquete local enlazado o una librería que trae su propio React agregan una segunda copia, y los hooks de una copia no funcionan con el renderizador de la otra. Ejecuta
npm ls reacty asegúrate de que aparezca una sola versión.
npm ls react
La vista previa de aquí funciona como un build de producción, que no imprime estos mensajes: un hook llamado desde un manejador de eventos falla con un TypeError simple como "Cannot read properties of null", y los errores propios de React llegan como códigos cortos "Minified React error". Ejecuta tu app en modo desarrollo para leer el texto completo.
Deja que el linter lo haga cumplir
eslint-plugin-react-hooks comprueba ambas reglas mientras escribes, y también los arrays de dependencias de los efectos. La plantilla de React de Vite y la configuración de ESLint de Next.js lo incluyen. En una configuración plana de ESLint, activa su preset recomendado:
// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';
export default defineConfig([reactHooks.configs.flat.recommended]);
Las dos reglas principales son react-hooks/rules-of-hooks (un error) y react-hooks/exhaustive-deps (una advertencia). Desde la versión 7 el preset también activa reglas que vienen del React Compiler, como react-hooks/purity y react-hooks/refs, que señalan los renderizados impuros y la lectura de refs durante el renderizado. Para elegir las reglas una por una, registra el plugin como plugins: { 'react-hooks': reactHooks } y enumera las reglas que quieras.
El plugin encuentra los hooks por su nombre: cualquier función cuyo nombre empieza con use seguido de una mayúscula se trata como un hook, y solo los componentes (nombres con mayúscula) y otras funciones use pueden llamarla. Por eso los hooks personalizados deben usar el prefijo.
La excepción: use
use, agregado en React 19, lee una promesa o un contexto, y a diferencia de todos los demás hooks se puede llamar dentro de una condición o un bucle. Igual tiene que llamarse durante el renderizado, dentro de un componente o un hook.
Solo el título con tema cambia de color cuando cambias el tema. Prueba lo mismo con useContext dentro del if y el linter lo marca. La página del hook use cubre cómo leer promesas con él.
Preguntas frecuentes
¿Cuáles son las reglas de los hooks?
Dos reglas. Llama a los hooks solo en el nivel superior de un componente de función o de un hook personalizado, nunca dentro de condiciones, bucles, funciones anidadas o después de un return temprano. Y llama a los hooks solo desde componentes de función o hooks personalizados, no desde funciones normales ni componentes de clase.
¿Por qué no se pueden llamar los hooks de forma condicional?
React no conoce tus hooks por su nombre. Guarda sus valores en una lista y los devuelve en el orden en que ocurren las llamadas. Si una condición se salta una llamada, cada hook posterior lee el valor que pertenecía al hook anterior.
¿Qué significa "Rendered more hooks than during the previous render"?
Un renderizado llamó a más hooks que el renderizado anterior, normalmente porque un hook está dentro de un if o después de un return temprano que esta vez se comportó distinto. Mueve todos los hooks por encima de la condición y pon la condición dentro del hook.
¿Cómo arreglo "Invalid hook call"?
Revisa tres cosas: que el hook se ejecute mientras React renderiza un componente de función (no en un manejador de eventos, el callback de un efecto, un componente de clase ni a nivel de módulo), que react y react-dom tengan versiones que coincidan, y que la app tenga una sola copia de React (npm ls react las enumera).
¿El hook use se puede llamar de forma condicional?
Sí. use (React 19) es la única excepción: puedes llamarlo dentro de bloques if y bucles. Igual tiene que llamarse dentro de un componente o un hook, no en un manejador de eventos.