DavidGadyan

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.

  1. 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> ); }
  2. 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>}
  3. 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
  4. 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 } }));
  5. 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]);
  6. 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>;
  7. 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} />
  8. 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
  9. 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} />
  10. 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
  11. 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; } }
  12. 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.