Llevas usando un hook desde la lección 02-04 sin haberle puesto nombre a la categoría: useState. Y en las dos últimas lecciones han aparecido las razones históricas por las que los hooks existen: el infierno de envoltorios de los HOC y las render props (04-02), y la lógica relacionada repartida entre métodos del ciclo de vida que estaban a cincuenta líneas unos de otros (04-03). Esta lección cierra ese círculo. Vas a entender qué es exactamente un hook, qué problema vino a resolver, por qué tiene dos reglas de uso aparentemente arbitrarias —y el mecanismo interno que las explica—, qué hooks existen y en qué lección del curso se ve cada uno. No desarrollaremos ninguno a fondo: ese es el trabajo del Módulo 5 completo. Aquí montamos el mapa antes de recorrer el territorio.

Contenido

  1. Qué problema vinieron a resolver los hooks
  2. Qué es un hook, con precisión
  3. Regla 1: solo en el nivel superior
  4. El mecanismo: la lista ordenada de hooks
  5. Regla 2: solo desde componentes o desde otros hooks
  6. eslint-plugin-react-hooks, la red de seguridad
  7. Catálogo de los hooks del curso
  8. Uso básico combinado: PanelResumen con useState + useEffect
  9. Estado local, referencia y contexto: tres cosas distintas

  1. Qué problema vinieron a resolver los hooks

Antes de 2019, un componente de React tenía que elegir entre dos formas incompatibles:

  • Componente de función: sencillo, legible, sin this… pero sin estado y sin acceso al ciclo de vida. Solo servía para presentación.
  • Componente de clase: podía todo, a cambio de constructor, bind, this y métodos de ciclo de vida.

Eso obligaba a un refactor completo en cuanto un componente de presentación necesitaba recordar un dato. Pero el problema de fondo era otro, y más grave: no había forma decente de reutilizar lógica con estado.

Supón que tres componentes de CicloUrbano necesitan saber el ancho de la ventana para decidir cuántas tarjetas caben. Con clases, la lógica —suscribirse al evento resize, guardar el ancho, darse de baja— había que copiarla en los tres, o envolver los tres en un HOC, o meterlos dentro de una render prop. Las dos últimas opciones producían árboles como este:

// Lo que se veía en React DevTools con cuatro HOC apilados
<conAutenticacion(conTema(conAncho(conRegistro(PanelOperario))))>
  <conTema(conAncho(conRegistro(PanelOperario)))>
    <conAncho(conRegistro(PanelOperario))>
      <conRegistro(PanelOperario)>
        <PanelOperario />   ← el único que pinta algo

Cuatro niveles que no dibujan nada, props que aparecen sin origen visible y una depuración incómoda. Eso es el infierno de envoltorios (wrapper hell) que viste en 04-02.

El segundo problema era la organización por momentos en lugar de por asuntos, que analizamos en 04-03:

componentDidMount() {
  this.cargarBicicletas();                                  // asunto A
  window.addEventListener('resize', this.medirAncho);       // asunto B
  this.temporizador = setInterval(this.actualizarReloj, 1000);  // asunto C
}

componentWillUnmount() {
  window.removeEventListener('resize', this.medirAncho);    // asunto B, aquí abajo
  clearInterval(this.temporizador);                         // asunto C, aquí abajo
}

Tres asuntos que no tienen nada que ver comparten método, y cada uno tiene su otra mitad en un método distinto. Es la receta perfecta para olvidarse de una limpieza.

Los hooks resuelven las tres cosas a la vez:

Problema anterior Respuesta de los hooks
Las funciones no podían tener estado useState y compañía funcionan en cualquier componente de función
Reutilizar lógica con estado exigía envoltorios Un hook personalizado es una llamada a función, sin nivel extra en el árbol
La lógica relacionada quedaba desperdigada Cada asunto va en su propio bloque, con su limpieza al lado
El this y los bind Desaparecen: no hay clase

Y una cosa que no hicieron: eliminar las clases. Siguen funcionando, y para los límites de error todavía son obligatorias (04-05).

  1. Qué es un hook, con precisión

Un hook es una función de JavaScript cuyo nombre empieza por use y que engancha el componente al sistema interno de React, permitiéndole acceder a capacidades como el estado, los efectos o el contexto.

Desmenucemos la definición:

  • Es una función normal. No es una palabra clave del lenguaje, no es sintaxis especial, no requiere compilación. useState es una función que React exporta y tú importas.
  • Su nombre empieza por use. No es decorativo: es la convención por la que React y las herramientas de análisis reconocen un hook. eslint-plugin-react-hooks decide si una función es un hook mirando su nombre, y aplica las reglas en consecuencia. Si escribes tu propia función con estado y la llamas obtenerAncho en vez de useAncho, las reglas no se aplicarán y perderás la red de seguridad.
  • Engancha con el sistema interno. Aquí está la clave conceptual. Un componente de función es solo una función: cuando termina, sus variables desaparecen. Entonces, ¿dónde se guarda el estado entre renders? Fuera del componente, en una estructura de datos interna de React, asociada a esa instancia concreta. El hook es el cable que conecta la función con ese almacén.
function ContadorPlazas() {
  const [plazas, setPlazas] = useState(20);
  // `plazas` es una variable local que muere al terminar la función.
  // El VALOR 20 (y luego 19, 18…) no vive aquí: vive dentro de React.
  // useState es el cable que va a buscarlo en cada render.
  return <p>Plazas libres: {plazas}</p>;
}

Esa idea —el estado vive en React, no en la función— es lo que hace posibles las dos reglas del apartado siguiente. Y también explica algo que ya observaste en 02-04: dos instancias del mismo componente tienen estados independientes, porque React guarda un almacén por instancia, no por componente.

  1. Regla 1: solo en el nivel superior

Llama a los hooks únicamente en el nivel superior del componente. Nunca dentro de condicionales, bucles, funciones anidadas ni después de un return anticipado.

// CORRECTO: los tres hooks, en el nivel superior, siempre en el mismo orden
function PanelReservaAvanzado({ bicicleta }) {
  const [horas, setHoras] = useState(1);
  const [confirmada, setConfirmada] = useState(false);
  const referencia = useRef(null);

  if (!bicicleta) {
    return <p>Selecciona una bicicleta.</p>;   // el return va DESPUÉS de los hooks
  }
  // …
}
// INCORRECTO: el hook está dentro de un if
function PanelReservaAvanzado({ bicicleta }) {
  if (bicicleta) {
    const [horas, setHoras] = useState(1);     // ❌
  }
  // …
}
// INCORRECTO: return anticipado ANTES de un hook
function PanelReservaAvanzado({ bicicleta }) {
  const [horas, setHoras] = useState(1);
  if (!bicicleta) return null;                 // ❌ el siguiente hook a veces no se ejecuta
  const [confirmada, setConfirmada] = useState(false);
  // …
}
// INCORRECTO: hook dentro de un bucle
function ListaContadores({ estaciones }) {
  return estaciones.map((estacion) => {
    const [plazas, setPlazas] = useState(estacion.plazas);   // ❌
    return <p key={estacion.id}>{plazas}</p>;
  });
}

El último caso tiene una solución conceptual, no técnica: si cada estación necesita su propio estado, cada estación necesita su propio componente. Extraes ContadorPlazas y llamas al hook dentro de él. Cada instancia tendrá su almacén, y la regla se cumple sola.

¿Por qué reglas tan estrictas? Porque el mecanismo interno de React depende del orden. Vamos a verlo.

  1. El mecanismo: la lista ordenada de hooks

React no guarda el estado por el nombre de la variable. No puede: const [horas, setHoras] es destructuring de un array, y React nunca ve el nombre horas. Lo que hace es mucho más simple: mantiene, para cada instancia de componente, una lista ordenada de celdas, y un puntero que avanza una posición con cada llamada a un hook.

flowchart LR
    subgraph R1["Primer render"]
      H1["useState(1)"] --> C1["celda 0<br/>valor: 1"]
      H2["useState(false)"] --> C2["celda 1<br/>valor: false"]
      H3["useRef(null)"] --> C3["celda 2<br/>valor: {current: null}"]
    end
    subgraph R2["Renders siguientes"]
      S1["useState(1)"] --> D1["lee celda 0"]
      S2["useState(false)"] --> D2["lee celda 1"]
      S3["useRef(null)"] --> D3["lee celda 2"]
    end
    R1 --> R2

En el primer render React crea las celdas en el orden en que se llaman los hooks. En todos los renders siguientes se limita a leerlas en el mismo orden. El vínculo entre useState(1) y su valor guardado es exclusivamente la posición.

De ahí sale la regla: si en un render se llaman tres hooks y en el siguiente solo dos, las posiciones se desplazan y cada variable recibe el valor de otra.

El desastre, paso a paso

// CÓDIGO ROTO, para entender el mecanismo
function PanelReserva({ bicicleta }) {
  const [horas, setHoras] = useState(1);          // celda 0

  if (bicicleta) {
    const [nota, setNota] = useState('');         // ❌ celda 1 solo a veces
  }

  const [confirmada, setConfirmada] = useState(false);   // ¿celda 1 o celda 2?
  // …
}
sequenceDiagram
    participant R1 as Render 1 (bicicleta = objeto)
    participant M as Celdas de React
    participant R2 as Render 2 (bicicleta = null)
    R1->>M: useState(1) → celda 0 = 1
    R1->>M: useState('') → celda 1 = ''
    R1->>M: useState(false) → celda 2 = false
    Note over M: [1, '', false]
    R2->>M: useState(1) → lee celda 0 = 1 ✅
    R2->>M: (el if no se cumple: no hay llamada)
    R2->>M: useState(false) → lee celda 1 = '' ❌
    Note over R2: `confirmada` vale '' (una cadena)<br/>en vez de false

El resultado en pantalla: confirmada pasa a valer '' en lugar de false, y setConfirmada escribe en la celda que pertenecía a nota. No hay ningún error de sintaxis, la aplicación no se detiene y el fallo se manifiesta como un comportamiento absurdo muy difícil de rastrear. React detecta muchos de estos casos y avisa con:

Warning: React has detected a change in the order of Hooks called by PanelReserva.
This will lead to bugs and errors if not fixed.

Cuando veas ese mensaje, busca un hook dentro de un if, de un bucle, de un try/catch o después de un return anticipado. Siempre es eso.

La consecuencia práctica: el número y el orden de las llamadas a hooks debe ser idéntico en todos los renders de un componente. Por eso los hooks se escriben todos juntos, arriba del todo, antes de cualquier lógica condicional. Y si necesitas un valor condicional, la condición va dentro del hook, no alrededor:

// En lugar de poner el hook dentro del if, pon el if dentro del hook
const [nota, setNota] = useState(bicicleta ? '' : 'sin bicicleta');

  1. Regla 2: solo desde componentes o desde otros hooks

Llama a los hooks solo desde componentes de función de React o desde otros hooks personalizados. Nunca desde funciones JavaScript normales.

// CORRECTO: desde un componente
function ResumenFlota({ flota }) {
  const [orden, setOrden] = useState('modelo');
  // …
}

// CORRECTO: desde un hook personalizado (nombre empieza por use)
function useFlotaOrdenada(flota) {
  const [orden, setOrden] = useState('modelo');
  return { orden, setOrden };
}

// INCORRECTO: desde una función normal
function calcularDisponibles(flota) {
  const [contador, setContador] = useState(0);   // ❌ ¿de qué componente es este estado?
  return flota.filter((b) => b.estado === 'disponible').length;
}

// INCORRECTO: desde un manejador de evento
function manejarClic() {
  const [abierto, setAbierto] = useState(false);  // ❌ se ejecuta fuera del render
}

La razón sale directamente del apartado anterior: las celdas pertenecen a una instancia de componente que se está renderizando ahora mismo. Si llamas a un hook desde una función suelta, React no sabe a qué lista de celdas dirigirse, y lanza:

Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

El caso del manejador de evento merece un aviso, porque es un error frecuente en quien empieza: los manejadores se ejecutan después del render, cuando ya no hay ninguna renderización en curso. Los hooks se llaman durante el render; los manejadores usan lo que esos hooks devolvieron.

// El patrón correcto
function TarjetaBicicleta({ bicicleta }) {
  const [destacada, setDestacada] = useState(false);   // hook: en el render

  function manejarClic() {
    setDestacada(!destacada);                          // manejador: usa el resultado
  }

  return <article onClick={manejarClic}>…</article>;
}

Y la consecuencia constructiva de esta regla es la que da sentido a todo: una función que empieza por use y llama a hooks es un hook personalizado, y eso es todo lo que hace falta para reutilizar lógica con estado. Sin envoltorios, sin niveles nuevos en el árbol. Lo construirás en Hooks Personalizados.

  1. eslint-plugin-react-hooks, la red de seguridad

Las dos reglas son fáciles de romper sin darse cuenta, sobre todo al refactorizar. Por eso el equipo de React mantiene un plugin de ESLint que las comprueba automáticamente, y que las plantillas de Vite para React ya traen configurado.

npm install --save-dev eslint-plugin-react-hooks
// eslint.config.js (fragmento)
import reactHooks from 'eslint-plugin-react-hooks';

export default [
  {
    plugins: { 'react-hooks': reactHooks },
    rules: {
      'react-hooks/rules-of-hooks': 'error',        // las dos reglas de esta lección
      'react-hooks/exhaustive-deps': 'warn'         // dependencias de los efectos (05-02)
    }
  }
];

Qué aporta cada regla:

Regla Qué detecta Severidad recomendada
rules-of-hooks Hooks en condicionales, bucles, funciones normales o manejadores error: no hay falsos positivos que justifiquen ignorarla
exhaustive-deps Dependencias que faltan o sobran en useEffect, useMemo y useCallback warn: casi siempre tiene razón, pero hay casos legítimos de excepción

La primera regla se equivoca tan poco que conviene tratarla como error de compilación. Si alguna vez te apetece silenciarla con un comentario, la respuesta correcta casi siempre es reestructurar el componente —extraer un componente hijo, mover la condición dentro del hook—, no callar el aviso.

  1. Catálogo de los hooks del curso

Este es el mapa completo. No memorices los detalles ahora: la columna de la derecha te dice dónde se explica cada uno a fondo.

Hook Para qué sirve, en una frase Dónde se estudia
useState Guardar un dato que cambia y provoca un nuevo render al cambiar 05-01
useEffect Sincronizar el componente con un sistema externo (temporizadores, red, suscripciones) 05-02
useRef Guardar un valor sin provocar renders, y acceder a nodos del DOM 05-03
useContext Leer un valor compartido por todo un subárbol, sin perforación de props 05-04
useReducer Gestionar estado complejo con muchas transiciones, mediante acciones y un reductor 05-05
useMemo Recordar el resultado de un cálculo caro entre renders 08-03
useCallback Recordar una función entre renders para no romper la memorización de los hijos 08-03
useId Generar un identificador único y estable para asociar label con campos 05-03 (mención) y accesibilidad de 03-06
useTransition Marcar una actualización como no urgente para no bloquear la interfaz Módulo 8
useDeferredValue Mostrar una versión «retrasada» de un valor mientras llega el definitivo Módulo 8
use Leer una promesa o un contexto directamente durante el render (React 19) Módulo 10
Hooks personalizados Empaquetar tu propia lógica con estado en una función reutilizable 05-06

Dos observaciones sobre el catálogo:

  • La mayoría de los componentes solo necesitan useState. Después, por frecuencia, van useEffect y useContext. useMemo y useCallback son herramientas de optimización que solo se aplican tras medir un problema, y llegan en el módulo 8 precisamente por eso.
  • Existen más hooks de los que aparecen aquí (useImperativeHandle, useSyncExternalStore, useDebugValue, useOptimistic, useActionState…). Son especializados y este curso los deja fuera salvo mención puntual. Con la tabla anterior cubres el noventa y cinco por ciento del React que se escribe a diario.

  1. Uso básico combinado: PanelResumen con useState + useEffect

Un aperitivo del módulo 5. Este componente de CicloUrbano combina los dos hooks que más usarás: guarda un contador de consultas del resumen y sincroniza el título de la pestaña del navegador con la flota disponible.

// src/componentes/PanelResumen.jsx
import { useState, useEffect } from 'react';
import estilos from './PanelResumen.module.css';

/**
 * Resumen consultable de la flota de CicloUrbano.
 * Props:
 *  - flota (array de Bicicleta, obligatorio)
 */
function PanelResumen({ flota }) {
  const [consultas, setConsultas] = useState(0);
  const [mensaje, setMensaje] = useState('Pulsa para actualizar el resumen.');

  // Valores DERIVADOS: se recalculan en cada render, no son estado (04-01)
  const disponibles = flota.filter((bicicleta) => bicicleta.estado === 'disponible').length;
  const enTaller = flota.filter((bicicleta) => bicicleta.estado === 'mantenimiento').length;

  // EFECTO: sincroniza el título del documento con el número de disponibles
  useEffect(() => {
    document.title = `CicloUrbano · ${disponibles} bicis disponibles`;
  }, [disponibles]);

  function manejarConsulta() {
    setConsultas((consultasPrevias) => consultasPrevias + 1);
    setMensaje(
      disponibles === 0
        ? 'No queda ninguna bicicleta disponible ahora mismo.'
        : `Hay ${disponibles} de ${flota.length} bicicletas listas para alquilar.`
    );
  }

  return (
    <section className={estilos.panel}>
      <h2>Resumen de la flota</h2>
      <p>Total: {flota.length} · Disponibles: {disponibles} · En taller: {enTaller}</p>
      <p aria-live="polite">{mensaje}</p>
      <p className={estilos.contador}>Consultas realizadas: {consultas}</p>
      <button type="button" onClick={manejarConsulta}>
        Actualizar resumen
      </button>
    </section>
  );
}

export default PanelResumen;

Qué hace cada pieza y por qué está donde está:

  • Los dos useState van al principio, en el nivel superior, uno detrás de otro. Ocupan las celdas 0 y 1, en ese orden, en todos los renders. Regla 1 cumplida.
  • disponibles y enTaller no son estado. Se calculan de flota en cada render. Si fueran estado, habría que recalcularlos a mano cada vez que cambia la flota, con el riesgo de desincronización que viste en 04-01.
  • setConsultas((consultasPrevias) => …) usa la forma funcional porque el valor nuevo depende del anterior. Es la precaución de 02-04.
  • El useEffect sincroniza con un sistema externo: el document.title pertenece al navegador, no a React. Aquí es donde se aplica el modelo mental de 04-03: no se trata de «ejecutar código al montar», sino de mantener el título sincronizado con disponibles. Por eso disponibles aparece en la lista de dependencias [disponibles]: cuando ese número cambie, la sincronización se rehará.
  • manejarConsulta no llama a ningún hook. Usa los resultados de los hooks (setConsultas, setMensaje, disponibles), que es exactamente el reparto correcto según la regla 2.
  • El aria-live="polite" viene de 03-06: el mensaje cambia sin que la persona usuaria mueva el foco, y hay que anunciarlo.

Y en App, sin ninguna ceremonia:

// src/App.jsx (fragmento)
<PanelResumen flota={bicicletas} />

Nada de HOC, nada de render props, ningún nivel extra en el árbol. Dos llamadas a función dentro del componente y ya tiene estado y efectos. Esa es la aportación de los hooks resumida en un ejemplo.

  1. Estado local, referencia y contexto: tres cosas distintas

Para cerrar el mapa, la distinción que más ordena las decisiones que tomarás en el módulo 5:

Herramienta Guarda un valor que… Provoca render al cambiar Alcance Se ve en
Estado (useState) Se ve en pantalla y cambia con el tiempo La instancia del componente 05-01
Referencia (useRef) Hay que recordar pero no se pinta (un identificador de temporizador, un nodo del DOM) No La instancia del componente 05-03
Contexto (useContext) Necesitan muchos componentes lejanos entre sí (usuario, tema, idioma) Sí, en quien lo consume Todo el subárbol bajo el proveedor 05-04

En una frase cada uno:

  • Estado: «lo que se ve, y cuando cambia hay que repintar».
  • Referencia: «lo que se recuerda entre renders sin que nadie tenga que repintarse». Es exactamente el this.temporizador de la clase PanelActividad de 04-03.
  • Contexto: «lo que está disponible para todo un subárbol sin ir de mano en mano», la respuesta a la perforación de props que quedó pendiente en 04-01.

Errores Comunes y Consejos

  • Hook dentro de un if, un bucle o un try. Rompe la correspondencia por posición y provoca fallos incomprensibles. Si necesitas condicionalidad, mueve la condición dentro del hook o extrae un componente hijo.
  • Hook después de un return anticipado. Es el mismo error disfrazado: el return hace que las líneas siguientes no se ejecuten en algunos renders. Todos los hooks van antes de cualquier return.
  • Llamar a un hook desde un manejador de evento. Los manejadores se ejecutan fuera del render. Llama al hook arriba y usa su resultado dentro del manejador.
  • Nombrar obtenerAlgo a una función que llama a hooks. Sin el prefijo use, el plugin de ESLint no la reconoce como hook y no comprueba nada dentro de ella. La convención es funcional, no estética.
  • Creer que useState en un bucle da «un estado por elemento». No lo da: da un desastre. Un estado por elemento significa un componente por elemento.
  • Silenciar rules-of-hooks con un comentario de ESLint. Es tratar el síntoma. La regla no se equivoca prácticamente nunca.
  • Consejo: escribe todos los hooks juntos, al principio del componente. Estado, referencias, contexto y efectos, en ese orden, y después la lógica derivada, los manejadores y el return. Si mantienes esa disposición en todo el proyecto, cumplir las reglas es automático.
  • Consejo: mira los hooks en React DevTools. Al seleccionar un componente de función verás la lista de sus hooks en orden, con sus valores. Es la lista de celdas del apartado 4, hecha visible.

Ejercicios

Ejercicio 1. Este componente de CicloUrbano rompe las reglas de los hooks en tres sitios distintos. Identifica cada infracción, explica qué regla incumple y qué consecuencia concreta tiene, y reescribe el componente correctamente.

function PanelEstacion({ estacion, mostrarDetalle }) {
  const [plazasLibres, setPlazasLibres] = useState(estacion.plazas);

  if (!estacion) {
    return <p>Estación no encontrada.</p>;
  }

  if (mostrarDetalle) {
    const [detalleAbierto, setDetalleAbierto] = useState(true);
  }

  function manejarAlquiler() {
    const [ultimoAlquiler, setUltimoAlquiler] = useState(null);
    setPlazasLibres(plazasLibres + 1);
  }

  return (
    <article>
      <h3>{estacion.nombre}</h3>
      <p>Plazas libres: {plazasLibres}</p>
      <button type="button" onClick={manejarAlquiler}>Devolver bicicleta</button>
    </article>
  );
}

Ejercicio 2. Sin escribir código, indica qué hook usarías en cada situación de CicloUrbano y en qué lección se estudia. Justifica brevemente cada elección.

  1. Recordar qué tipo de bicicleta está filtrado en el catálogo.
  2. Guardar el identificador de un setTimeout para poder cancelarlo.
  3. Que quince componentes repartidos por el árbol conozcan el usuario que ha iniciado sesión.
  4. Mantener el document.title al día con el número de reservas activas.
  5. Gestionar un formulario de reserva con ocho campos y transiciones complejas entre estados de validación.

Ejercicio 3. Amplía el PanelResumen del apartado 8 con un tercer trozo de estado, ultimaConsulta, que guarde la hora de la última consulta como una cadena legible (toLocaleTimeString('es-ES')), o null si todavía no se ha consultado. Muéstralo en pantalla solo cuando exista. Después responde: ¿en qué posición de la lista de celdas queda ese nuevo estado y por qué es importante dónde lo declares?

Soluciones

Solución 1. Las tres infracciones:

  1. return anticipado antes de un hook (regla 1). Cuando estacion es nulo, la función termina antes de llegar a las llamadas siguientes, y el número de hooks ejecutados cambia entre renders. Además, useState(estacion.plazas) en la línea anterior ya habría reventado al leer .plazas de null: la guarda llega tarde.
  2. useState dentro de un if (regla 1). detalleAbierto solo se crea cuando mostrarDetalle es verdadero; al cambiar esa prop, las celdas se desplazan y los estados se mezclan entre sí.
  3. useState dentro de un manejador de evento (regla 2). manejarAlquiler se ejecuta después del render, cuando no hay renderización en curso: React lanza «Invalid hook call».

Y un cuarto problema, no de reglas sino de corrección: setPlazasLibres(plazasLibres + 1) debería usar la forma funcional, y además no tiene tope superior (podría superar las plazas totales de la estación).

function PanelEstacion({ estacion, mostrarDetalle }) {
  // 1. TODOS los hooks arriba, sin condiciones y antes de cualquier return
  const [plazasLibres, setPlazasLibres] = useState(estacion ? estacion.plazas : 0);
  const [detalleAbierto, setDetalleAbierto] = useState(true);
  const [ultimoAlquiler, setUltimoAlquiler] = useState(null);

  // 2. Los returns anticipados, DESPUÉS de los hooks
  if (!estacion) {
    return <p>Estación no encontrada.</p>;
  }

  // 3. El manejador usa los resultados de los hooks; no llama a ninguno
  function manejarAlquiler() {
    setPlazasLibres((previas) => Math.min(estacion.plazas, previas + 1));
    setUltimoAlquiler(new Date().toLocaleTimeString('es-ES'));
  }

  return (
    <article>
      <h3>{estacion.nombre}</h3>
      <p>Plazas libres: {plazasLibres}</p>
      {mostrarDetalle && detalleAbierto && <p>Barrio: {estacion.barrio}</p>}
      {ultimoAlquiler && <p>Última devolución: {ultimoAlquiler}</p>}
      <button type="button" onClick={manejarAlquiler}>Devolver bicicleta</button>
    </article>
  );
}

Observa el patrón general de la corrección: los hooks se declaran todos, siempre; lo condicional es el uso de su valor en el JSX, no la llamada.

Solución 2.

Caso Hook Lección Por qué
1. Tipo filtrado en el catálogo useState 05-01 Es un dato visible que cambia con la interacción y debe provocar un nuevo render. Vive en App desde 04-01
2. Identificador de un setTimeout useRef 05-03 Hay que recordarlo entre renders pero no se pinta; cambiarlo no debe repintar nada. Es el this.temporizador de 04-03
3. Usuario de la sesión, en quince componentes useContext 05-04 Elevarlo a App y pasarlo por props provocaría la perforación de props anunciada en 04-01
4. document.title al día useEffect 05-02 El título del documento es un sistema externo a React; hay que sincronizarlo con un valor del componente
5. Formulario de ocho campos con transiciones useReducer 05-05 Con muchos campos y reglas de transición, un reductor centraliza la lógica mejor que ocho useState sueltos

Solución 3.

function PanelResumen({ flota }) {
  const [consultas, setConsultas] = useState(0);                 // celda 0
  const [mensaje, setMensaje] = useState('Pulsa para actualizar el resumen.');  // celda 1
  const [ultimaConsulta, setUltimaConsulta] = useState(null);    // celda 2

  const disponibles = flota.filter((bicicleta) => bicicleta.estado === 'disponible').length;
  const enTaller = flota.filter((bicicleta) => bicicleta.estado === 'mantenimiento').length;

  useEffect(() => {
    document.title = `CicloUrbano · ${disponibles} bicis disponibles`;
  }, [disponibles]);

  function manejarConsulta() {
    setConsultas((consultasPrevias) => consultasPrevias + 1);
    setUltimaConsulta(new Date().toLocaleTimeString('es-ES'));
    setMensaje(
      disponibles === 0
        ? 'No queda ninguna bicicleta disponible ahora mismo.'
        : `Hay ${disponibles} de ${flota.length} bicicletas listas para alquilar.`
    );
  }

  return (
    <section className={estilos.panel}>
      <h2>Resumen de la flota</h2>
      <p>Total: {flota.length} · Disponibles: {disponibles} · En taller: {enTaller}</p>
      <p aria-live="polite">{mensaje}</p>
      <p className={estilos.contador}>Consultas realizadas: {consultas}</p>
      {ultimaConsulta && <p>Última consulta: {ultimaConsulta}</p>}
      <button type="button" onClick={manejarConsulta}>Actualizar resumen</button>
    </section>
  );
}

ultimaConsulta ocupa la celda 2, porque es el tercer hook llamado. Lo importante no es el número en sí, sino que esa posición sea la misma en todos los renders: por eso la declaración va en el nivel superior, junto a las otras dos, y no dentro del if que decide si mostrarla. La condición se aplica solo al JSX ({ultimaConsulta && …}), nunca a la llamada al hook. Si lo hubieras declarado dentro de un condicional, en los renders donde no se cumpliera, useEffect pasaría a leer la celda equivocada.

Conclusión

Un hook es una función que empieza por use y engancha un componente de función al sistema interno de React, dándole acceso a estado, efectos, contexto y todo lo que antes exigía una clase. Nacieron para resolver tres problemas concretos que arrastraban las clases: la imposibilidad de dar estado a una función, el infierno de envoltorios que producían los HOC y las render props (04-02), y la lógica de un mismo asunto repartida entre métodos del ciclo de vida (04-03).

Sus dos reglas —solo en el nivel superior y solo desde componentes o desde otros hooks— no son caprichos de estilo: se explican por el mecanismo de la lista ordenada de celdas que React mantiene por instancia. El vínculo entre una llamada a useState y su valor guardado es la posición, y por eso el número y el orden de las llamadas debe ser idéntico en todos los renders. eslint-plugin-react-hooks vigila las dos reglas por ti, y conviene tratar rules-of-hooks como error. Tienes también el mapa completo: qué hooks existen, para qué sirve cada uno y en qué lección se estudia; y un primer PanelResumen de CicloUrbano que combina useState con useEffect sin un solo envoltorio.

Queda una pieza para cerrar el módulo, y es la excepción que confirma todo lo anterior. Los hooks han sustituido a los métodos del ciclo de vida en todo… menos en un caso: capturar los errores que lanza un componente durante el render. Si una tarjeta de CicloUrbano revienta por un dato inesperado, React desmonta el árbol entero y la persona usuaria se queda mirando una página en blanco. Evitarlo requiere una herramienta que a día de hoy sigue exigiendo un componente de clase. La próxima lección es Límites de Error: Capturar Fallos en la Interfaz.

Curso de React

Módulo 1: Introducción a React

Módulo 2: Componentes de React

Módulo 3: Trabajando con Eventos

Módulo 4: Conceptos Avanzados de Componentes

Módulo 5: Hooks de React

Módulo 6: Enrutamiento en React

Módulo 7: Gestión del Estado

Módulo 8: Optimización del Rendimiento

Módulo 9: Pruebas en React

Módulo 10: Temas Avanzados

Módulo 11: Proyecto: Construyendo una Aplicación Completa

© Copyright 2026. Todos los derechos reservados