← React & TypeScript

Hooks

¿Cuáles son las reglas de los hooks y por qué existen? ¿Qué pasa si llamo un hook dentro de un if?

Ver respuesta — intenta responderla en voz alta primero

Hay dos reglas: llamar los hooks solo en el nivel superior del componente (nunca dentro de condicionales, bucles o funciones anidadas) y llamarlos solo desde componentes de React o desde custom hooks. Existen porque React identifica cada hook por su ORDEN de llamada; si el orden cambia entre renders, React mezcla los estados.

Cuando escribes useState("Ana"), no le pasas a React ningún nombre ni ID. Entonces, ¿cómo sabe React qué estado te corresponde en el siguiente render? Por la posición: React guarda los hooks de cada componente en una lista y los va entregando en orden. Primer hook llamado, primera casilla; segundo hook, segunda casilla.

Los puntos clave:

  • Regla 1: solo en el nivel superior. Nada de hooks dentro de if, for, while, callbacks o después de un return temprano. Así el componente llama SIEMPRE los mismos hooks en el MISMO orden.
  • Regla 2: solo en componentes o custom hooks. No en funciones normales de JavaScript ni en clases, porque solo dentro de un render React tiene esa lista preparada.
  • El porqué: si un render llama 3 hooks y el siguiente llama 2 (porque un if se saltó uno), la lista se desalinea: el estado de la casilla 2 se entrega al hook equivocado. React detecta el cambio de orden y lanza un error.
  • La condición va DENTRO del hook, no alrededor. En vez de condicionar la llamada a useEffect, llama siempre al hook y pon el if dentro de su función.
  • El plugin eslint-plugin-react-hooks detecta estas violaciones automáticamente; en entrevista suma mencionarlo.
import { useEffect, useState } from "react";

type Props = { usuarioId: number | null };

function PerfilUsuario({ usuarioId }: Props) {
  // Nivel superior: estos hooks corren en TODOS los renders,
  // siempre en el mismo orden (casillas 1 y 2 de la lista interna)
  const [nombre, setNombre] = useState<string | null>(null);

  useEffect(() => {
    // La condición vive DENTRO del efecto, no alrededor del hook
    if (usuarioId === null) return;

    const controlador = new AbortController();
    fetch(`/api/usuarios/${usuarioId}`, { signal: controlador.signal })
      .then(res => res.json())
      .then((datos: { nombre: string }) => setNombre(datos.nombre))
      .catch(() => {}); // ignoramos la cancelación

    return () => controlador.abort(); // cleanup: cancela peticiones viejas
  }, [usuarioId]);

  // Los returns tempranos van DESPUÉS de todos los hooks
  if (usuarioId === null) return <p>Selecciona un usuario</p>;

  return <h2>{nombre ?? "Cargando..."}</h2>;
}

Condicionar la llamada de un hook, cambiando cuántos hooks corren en cada render:

// MAL: en unos renders hay 2 hooks y en otros solo 1. React desalinea
// su lista interna y lanza "Rendered fewer hooks than expected"
function Perfil({ activo }: { activo: boolean }) {
  if (activo) {
    const [nombre] = useState("Ana"); // hook dentro de un if
  }
  const [edad] = useState(30);
}

// BIEN: los hooks siempre corren; el return temprano va después
function PerfilCorregido({ activo }: { activo: boolean }) {
  const [nombre] = useState("Ana");
  const [edad] = useState(30);
  if (!activo) return null;
  return <p>{nombre}, {edad}</p>;
}

Variante del mismo error: un return temprano ANTES de un useEffect; en los renders donde se ejecuta el return, ese effect desaparece y el orden se rompe igual.

Son dos reglas: los hooks solo se llaman en el nivel superior del componente, nunca dentro de condicionales, bucles o después de un return temprano; y solo se llaman desde componentes de React o desde custom hooks. La razón es que React no identifica los hooks por nombre sino por orden de llamada: guarda una lista por componente y en cada render la recorre en el mismo orden. Si un if se salta un hook, la lista se desalinea y un estado termina asignado al hook equivocado, por eso React lanza un error. En la práctica, la condición va dentro del hook y el linter de react-hooks me avisa si rompo la regla.

Reto rápido

Este componente rompe las reglas de los hooks de dos formas. Encuéntralas y corrígelo:

function Notificaciones({ visible }: { visible: boolean }) {
  if (!visible) return null;
  const [cantidad, setCantidad] = useState(0);
  useEffect(() => { document.title = `(${cantidad}) notificaciones`; }, [cantidad]);
  return <button onClick={() => setCantidad(c => c + 1)}>+1</button>;
}
Ver solución
import { useEffect, useState } from "react";

// Problema: el return temprano estaba ANTES de los hooks, así que useState
// y useEffect se saltaban cuando visible era false y el orden cambiaba.
function Notificaciones({ visible }: { visible: boolean }) {
  const [cantidad, setCantidad] = useState(0); // 1. hooks primero, siempre

  useEffect(() => {
    if (!visible) return; // 2. la condición vive dentro del efecto
    document.title = `(${cantidad}) notificaciones`;
  }, [visible, cantidad]);

  if (!visible) return null; // 3. el return temprano va después de los hooks

  return <button onClick={() => setCantidad(c => c + 1)}>+1</button>;
}