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
- Qué problema vinieron a resolver los hooks
- Qué es un hook, con precisión
- Regla 1: solo en el nivel superior
- El mecanismo: la lista ordenada de hooks
- Regla 2: solo desde componentes o desde otros hooks
eslint-plugin-react-hooks, la red de seguridad- Catálogo de los hooks del curso
- Uso básico combinado:
PanelResumenconuseState+useEffect - Estado local, referencia y contexto: tres cosas distintas
- 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,thisy 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 algoCuatro 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).
- Qué es un hook, con precisión
Un hook es una función de JavaScript cuyo nombre empieza por
usey 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.
useStatees 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-hooksdecide 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 llamasobtenerAnchoen vez deuseAncho, 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.
- 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
returnanticipado.
// 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.
- 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');
- 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:
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.
eslint-plugin-react-hooks, la red de seguridad
eslint-plugin-react-hooks, la red de seguridadLas 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.
// 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.
- 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, vanuseEffectyuseContext.useMemoyuseCallbackson 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.
- Uso básico combinado:
PanelResumen con useState + useEffect
PanelResumen con useState + useEffectUn 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
useStatevan 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. disponiblesyenTallerno son estado. Se calculan deflotaen 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
useEffectsincroniza con un sistema externo: eldocument.titlepertenece 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 condisponibles. Por esodisponiblesaparece en la lista de dependencias[disponibles]: cuando ese número cambie, la sincronización se rehará. manejarConsultano 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:
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.
- 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 | Sí | 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.temporizadorde la clasePanelActividadde 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 untry. 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
returnanticipado. Es el mismo error disfrazado: elreturnhace que las líneas siguientes no se ejecuten en algunos renders. Todos los hooks van antes de cualquierreturn. - 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
obtenerAlgoa una función que llama a hooks. Sin el prefijouse, 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
useStateen 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-hookscon 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.
- Recordar qué tipo de bicicleta está filtrado en el catálogo.
- Guardar el identificador de un
setTimeoutpara poder cancelarlo. - Que quince componentes repartidos por el árbol conozcan el usuario que ha iniciado sesión.
- Mantener el
document.titleal día con el número de reservas activas. - 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:
returnanticipado antes de un hook (regla 1). Cuandoestaciones 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.plazasdenull: la guarda llega tarde.useStatedentro de unif(regla 1).detalleAbiertosolo se crea cuandomostrarDetallees verdadero; al cambiar esa prop, las celdas se desplazan y los estados se mezclan entre sí.useStatedentro de un manejador de evento (regla 2).manejarAlquilerse 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
- ¿Qué es React?
- Configuración del Entorno de Desarrollo
- Hola Mundo en React
- JSX: Extensión de Sintaxis de JavaScript
- Cómo Renderiza React: Virtual DOM y Reconciliación
Módulo 2: Componentes de React
- Entendiendo los Componentes
- Componentes Funcionales vs de Clase
- Props: Pasando Datos a Componentes
- State: Gestión del Estado del Componente
- Estilos en los Componentes: CSS, Módulos y Utilidades
Módulo 3: Trabajando con Eventos
- Manejo de Eventos en React
- Renderizado Condicional
- Listas y Claves
- Formularios y Componentes Controlados
- Validación de Formularios y Componentes No Controlados
- Accesibilidad en Componentes Interactivos
Módulo 4: Conceptos Avanzados de Componentes
- Elevando el Estado
- Composición vs Herencia
- Métodos del Ciclo de Vida de React
- Hooks: Introducción y Uso Básico
- Límites de Error: Capturar Fallos en la Interfaz
Módulo 5: Hooks de React
- Hook useState
- Hook useEffect
- Hook useRef y Acceso al DOM
- Hook useContext
- Hook useReducer
- Hooks Personalizados
Módulo 6: Enrutamiento en React
- Introducción a React Router
- Configuración de React Router
- Rutas Anidadas
- Navegación Programática
- Rutas Protegidas y Control de Acceso
Módulo 7: Gestión del Estado
- Introducción a la Gestión del Estado
- API de Contexto
- Redux: Introducción y Configuración
- Redux: Acciones y Reductores
- Redux: Conectando a React
- Estado del Servidor: Peticiones, Caché y Sincronización
Módulo 8: Optimización del Rendimiento
- Técnicas de Optimización del Rendimiento en React
- Memorización con React.memo
- Hooks useMemo y useCallback
- División de Código y Carga Perezosa
- Medir el Rendimiento con React DevTools Profiler
Módulo 9: Pruebas en React
- Introducción a las Pruebas
- Pruebas Unitarias con Jest
- Pruebas de Componentes con React Testing Library
- Pruebas de Código Asíncrono y Simulación de APIs
- Pruebas de Extremo a Extremo con Cypress
Módulo 10: Temas Avanzados
- Renderizado del Lado del Servidor (SSR) con Next.js
- Generación de Sitios Estáticos (SSG) con Next.js
- Suspense y React Server Components
- TypeScript con React
- React Native: Creación de Aplicaciones Móviles
