React: chuleta de hooks y quiz de práctica
Doce patrones de React detrás de la mayoría de preguntas de pruebas técnicas: componentes, useState, actualizaciones inmutables, efectos, formularios, memoización y el App Router de Next.js, con un quiz de práctica de 20 preguntas.
Chuleta de sintaxis
12 patrones. Elige uno en el mapa para ver su sintaxis, cuándo usarlo y la trampa que esconde.
Básicos
Componentes y props
Funciones que devuelven UI
- Sintaxis
function User({ name, age = 0, children }) { return <h1>Hello {name} ({age}){children}</h1>; } <User name="David" age={33}>!</User>- Cuándo usarlo
- Cada trozo de interfaz. Las props fluyen del padre al hijo; el hijo lee sus props y nunca las modifica.
- Qué hace
- Un componente es una función de props que devuelve JSX. React la vuelve a llamar cuando cambian sus props o su estado. children es lo que haya entre la etiqueta de apertura y la de cierre.
- Cuidado con
- Las props son de solo lectura. Los nombres de componente deben empezar por mayúscula o JSX los trata como etiquetas HTML. Devuelve un solo elemento raíz o un fragmento <>…</>.
- Ejemplo
- function Card({ title, children }) { return ( <section className="card"> <h2>{title}</h2> {children} </section> ); }
Básicos
Listas, keys y condicionales
Renderizar colecciones
- Sintaxis
{users.map(u => <User key={u.id} user={u} />)} {loading ? <Spinner /> : <List />} {error && <p role="alert">{error}</p>}- Cuándo usarlo
- Cualquier array que mostrar, y cualquier interfaz que dependa del estado: cargando, error, vacío.
- Qué hace
- Las keys permiten a React emparejar los elementos de una lista entre renders para que los nodos del DOM y el estado sigan con el elemento correcto. Los condicionales son expresiones JavaScript normales entre llaves.
- Cuidado con
- key={index} falla cuando la lista se reordena, se filtra o recibe inserciones: usa un id estable. count && <X /> renderiza 0 cuando count es 0: escribe count > 0 &&.
- Ejemplo
- {items.length === 0 ? <Empty /> : <ul>{items.map(it => <li key={it.id}>{it.name}</li>)}</ul>}
Estado
useState y actualizaciones funcionales
Estado local
- Sintaxis
const [count, setCount] = useState(0); setCount(count + 1); // uses this render's value setCount(prev => prev + 1); // uses the latest value- Cuándo usarlo
- Cualquier valor que cambie con el tiempo y deba volver a renderizar el componente: inputs, interruptores, datos cargados, contadores.
- Qué hace
- useState devuelve el valor actual y un setter; llamar al setter programa un nuevo render con el valor nuevo. Varias actualizaciones dentro de un mismo evento se agrupan en un solo render.
- Cuidado con
- Dos llamadas setCount(count + 1) en un mismo manejador suman 1, no 2, porque count es una instantánea de este render: usa la forma funcional. El estado pertenece a la instancia del componente, no se comparte entre instancias.
- Ejemplo
- function handleClick() { setCount(prev => prev + 1); setCount(prev => prev + 1); // +2 } const [form, setForm] = useState({ name: '', email: '' }); const [items, setItems] = useState(() => expensiveInitial()); // lazy initial state
Estado
Actualizaciones inmutables del estado
Nunca mutar
- Sintaxis
setUser(prev => ({ ...prev, name: 'John' })); // change a field setUsers(prev => [...prev, newUser]); // add setUsers(prev => prev.filter(u => u.id !== id)); // remove setUsers(prev => prev.map(u => u.id === id ? { ...u, done: true } : u)); // edit one- Cuándo usarlo
- Cada objeto o array guardado en el estado.
- Qué hace
- React compara referencias para decidir qué ha cambiado; un objeto o array nuevo señala una actualización, uno mutado parece idéntico y puede ignorarse.
- Cuidado con
- user.name = "x"; setUser(user) reutiliza la misma referencia y puede no renderizar. push, splice, sort y reverse mutan; spread, concat, filter, map y toSorted no.
- Ejemplo
- // wrong: same reference users.push(newUser); setUsers(users); // right: new array setUsers(prev => [...prev, newUser]); // nested field setState(prev => ({ ...prev, address: { ...prev.address, city } }));
Efectos
useEffect y limpieza
Sincronizar con el exterior
- Sintaxis
useEffect(() => { const id = setInterval(tick, 1000); return () => clearInterval(id); // cleanup }, []); // deps: once after mount useEffect(() => { fetchUser(userId); }, [userId]); // when userId changes- Cuándo usarlo
- Suscripciones, temporizadores, APIs del DOM, cargar datos cuando cambia un id. No para derivar valores de props o estado: calcúlalos durante el render.
- Qué hace
- El efecto se ejecuta después del commit: con [] solo tras el primer render, con [a, b] cuando cambian a o b, sin array tras cada render. La función devuelta se ejecuta antes de la siguiente ejecución y al desmontar.
- Cuidado con
- Una dependencia que falta lee valores obsoletos. Cambiar el estado sin condición dentro de un efecto que depende de ese estado crea un bucle infinito. En desarrollo, Strict Mode ejecuta los efectos dos veces para probar la limpieza.
- Ejemplo
- useEffect(() => { const ctrl = new AbortController(); fetch(`/api/users/${id}`, { signal: ctrl.signal }) .then(r => r.json()) .then(setUser) .catch(() => {}); return () => ctrl.abort(); }, [id]);
Efectos
Cargar datos con loading y error
Tres estados
- Sintaxis
const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let alive = true; fetch('/api/users') .then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); }) .then(data => { if (alive) setUsers(data); }) .catch(e => { if (alive) setError(e.message); }) .finally(() => { if (alive) setLoading(false); }); return () => { alive = false; }; }, []);- Cuándo usarlo
- Un componente que carga sus propios datos. En Next.js, mejor cargar en un Server Component y pasar los datos como props.
- Qué hace
- Cargando, error y datos son tres estados explícitos y el componente renderiza uno de ellos. La bandera alive ignora resultados que llegan tras desmontar o tras cambiar el id.
- Cuidado con
- fetch no lanza error con 404 o 500: comprueba r.ok. El callback del efecto no puede ser async; define una función async dentro y llámala.
- Ejemplo
- if (loading) return <p>Loading…</p>; if (error) return <p role="alert">{error}</p>; return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
Formularios
Inputs controlados y formularios
El estado manda en el input
- Sintaxis
const [name, setName] = useState(''); <form onSubmit={e => { e.preventDefault(); save(name); }}> <input value={name} onChange={e => setName(e.target.value)} /> <button type="submit">Save</button> </form>- Cuándo usarlo
- Validación mientras se escribe, campos dependientes, reiniciar un formulario, todo lo que exija que React conozca el valor en todo momento.
- Qué hace
- Un input controlado muestra el valor del estado e informa de cada cambio, así que el estado es la única fuente de verdad. Un input no controlado guarda su propio valor, que se lee con una ref cuando hace falta.
- Cuidado con
- value sin onChange deja el input en solo lectura y React avisa. Olvidar preventDefault recarga la página al enviar. Los checkbox se enlazan con checked, no con value.
- Ejemplo
- const [form, setForm] = useState({ email: '', agree: false }); const onChange = e => { const { name, type, value, checked } = e.target; setForm(prev => ({ ...prev, [name]: type === 'checkbox' ? checked : value })); }; <input name="email" value={form.email} onChange={onChange} /> <input name="agree" type="checkbox" checked={form.agree} onChange={onChange} />
Estado
Props frente a estado, elevar el estado
Dónde viven los datos
- Sintaxis
function Parent() { const [query, setQuery] = useState(''); return ( <> <SearchBox value={query} onChange={setQuery} /> <Results query={query} /> </> ); }- Cuándo usarlo
- Dos componentes hermanos necesitan el mismo valor: súbelo al padre común más cercano y pásalo hacia abajo con un callback setter.
- Qué hace
- Las props son entrada externa que posee el padre; el estado son datos internos que posee el componente. Elevar el estado mantiene una sola fuente de verdad para que los hermanos nunca discrepen.
- Cuidado con
- Copiar una prop al estado (useState(props.value)) la congela en el montaje. No guardes lo que puedas calcular: deriva fullName de first y last durante el render.
- Ejemplo
- function SearchBox({ value, onChange }) { return <input value={value} onChange={e => onChange(e.target.value)} />; } const fullName = `${first} ${last}`; // derived, not state
Rendimiento
useMemo, useCallback, React.memo
Evitar trabajo repetido
- Sintaxis
const sorted = useMemo(() => [...items].sort(byName), [items]); const onSelect = useCallback(id => setSelected(id), []); const Row = React.memo(function Row({ item, onSelect }) { /* … */ });- Cuándo usarlo
- Un cálculo es caro de forma medible, o un hijo memoizado vuelve a renderizar porque recibe una función u objeto nuevos en cada render.
- Qué hace
- useMemo cachea un valor y useCallback una función hasta que cambian las dependencias. React.memo evita renderizar un componente cuyas props son iguales (comparación superficial) a las de la vez anterior.
- Cuidado con
- Memoizar todo cuesta más de lo que ahorra: mide primero. Una prop con objeto o arrow function en línea anula React.memo. El React Compiler hace la mayor parte de esto automáticamente en proyectos nuevos.
- Ejemplo
- // new object every render: Row re-renders anyway <Row style={{ color: 'red' }} /> // stable reference: hoist it or memoise it const style = useMemo(() => ({ color: 'red' }), []); <Row style={style} />
Más hooks
useRef
Una caja que sobrevive a los renders
- Sintaxis
const inputRef = useRef(null); <input ref={inputRef} /> inputRef.current.focus(); // in a handler or effect const timer = useRef(null); // mutable value, no re-render timer.current = setTimeout(fn, 500);- Cuándo usarlo
- Enfocar o medir nodos del DOM, guardar temporizadores, valores anteriores o cualquier dato mutable que no deba provocar un render.
- Qué hace
- useRef devuelve el mismo objeto { current } en cada render; cambiar current no vuelve a renderizar, a diferencia del estado.
- Cuidado con
- No leas ni escribas ref.current durante el render (el React Compiler lo marca): úsalo en manejadores y efectos. Una ref es por instancia, como el estado.
- Ejemplo
- useEffect(() => { inputRef.current?.focus(); }, []); const prev = useRef(value); useEffect(() => { prev.current = value; }, [value]); // previous value
Más hooks
useContext y useReducer
Estado compartido y complejo
- Sintaxis
const ThemeContext = createContext('light'); <ThemeContext value="dark"><App /></ThemeContext> const theme = useContext(ThemeContext); const [state, dispatch] = useReducer(reducer, initial); dispatch({ type: 'add', item });- Cuándo usarlo
- Contexto para valores que necesitan muchos componentes (tema, usuario, idioma) sin pasar props en cadena. useReducer cuando varios campos cambian a la vez o una actualización depende de un tipo de acción.
- Qué hace
- Cada consumidor vuelve a renderizar cuando cambia el valor provisto. Un reducer es una función pura (state, action) => newState, así que las actualizaciones son predecibles y fáciles de probar.
- Cuidado con
- Un valor de provider creado en línea ({ user, setUser }) cambia en cada render y vuelve a renderizar todos los consumidores: memoízalo. El contexto no es un store; para actualizaciones frecuentes separa contextos o usa una librería de estado.
- Ejemplo
- function reducer(state, action) { switch (action.type) { case 'add': return [...state, action.item]; case 'remove': return state.filter(i => i.id !== action.id); default: return state; } }
Next.js
Lo esencial del App Router de Next.js
Servidor primero
- Sintaxis
app/page.tsx // route: / app/users/[id]/page.tsx // dynamic: /users/42 app/layout.tsx // shared shell 'use client'; // first line: Client Component export function generateStaticParams() { return ids.map(id => ({ id })); } import { redirect } from 'next/navigation';- Cuándo usarlo
- Server Components por defecto (acceso a datos, cero bundle en el cliente); "use client" solo para estado, efectos, APIs del navegador y manejadores de eventos.
- Qué hace
- Las carpetas bajo app/ definen rutas, [id] un segmento dinámico, layout.tsx la interfaz compartida. generateStaticParams enumera las páginas dinámicas que se prerenderizan; el middleware ejecuta lógica en cada petición: autenticación, rewrites y redirecciones.
- Cuidado con
- Los secretos van en variables de entorno solo de servidor, nunca NEXT_PUBLIC_*. Un Client Component no puede importar un Server Component, pero puede recibirlo como children. Usa next/image para imágenes optimizadas.
- Ejemplo
- // app/users/[id]/page.tsx (Server Component) export default async function Page({ params }) { const { id } = await params; const user = await db.user.find(id); // server only, never shipped return <Profile user={user} />; }
Elige un patrón en el mapa para ver su sintaxis, cuándo usarlo, qué hace y con qué tener cuidado.
Quiz de práctica
20 preguntas tipo test elegidas al azar entre las 38 de este tema, una por pantalla, corregidas al final. Cada pregunta remite a un patrón del mapa.
Elige una respuesta, muévete libremente entre las preguntas y termina cuando quieras: la puntuación y la explicación de cada pregunta aparecen al final.
- 20 preguntas
- Sin límite de tiempo
- Corrección al final
El quiz necesita JavaScript. La chuleta de arriba funciona sin él.
Material de práctica para pruebas técnicas cronometradas. El comportamiento descrito sigue la documentación de React 19 y del App Router de Next.js 16.
