InterviewHack.ai
Start free
Blog/Preguntas de entrevista de React con código (50+ con respuestas)

Preguntas de entrevista de React con código (50+ con respuestas)

16 de septiembre de 2026

reactjavascript

Artículo completo de 50+ preguntas de entrevista de React con código real, insertado en Supabase en blog_posts con slug preguntas-entrevista-react-con-codigo-50-respuestas, lang es, topic es-entrevista-react-espanol.

Preguntas de entrevista de React con código (50+ con respuestas)

Esta guía cubre más de 50 preguntas reales que aparecen en entrevistas técnicas de React — de junior a senior. Cada respuesta incluye código que podés ejecutar, la explicación del *por qué*, y lo que el entrevistador realmente quiere escuchar. Sin relleno.


NIVEL JUNIOR — Fundamentos y JSX

1. ¿Qué es JSX y a qué se transforma en tiempo de compilación?

JSX no es HTML. Es syntactic sugar sobre React.createElement. Babel lo transforma así:

jsx
// Lo que escribís
const el = <h1 className="titulo">Hola</h1>;

// Lo que ve el motor JavaScript
const el = React.createElement("h1", { className: "titulo" }, "Hola");

Lo que busca el entrevistador: que entiendas que JSX es una abstracción, no HTML real. Si lo sabés, podés razonar sobre por qué className en lugar de class, por qué las expresiones van entre {}, y por qué un componente tiene que empezar con mayúscula.


2. ¿Cuál es la diferencia entre un componente funcional y uno de clase?

jsx
// Componente de clase (legacy)
class Saludo extends React.Component {
  render() {
    return <p>Hola, {this.props.nombre}</p>;
  }
}

// Componente funcional (moderno — preferido)
function Saludo({ nombre }) {
  return <p>Hola, {nombre}</p>;
}

Los componentes funcionales son más simples, no necesitan this, y desde React 16.8 soportan estado y ciclo de vida con hooks. Los de clase siguen funcionando pero son legacy — en entrevistas es raro que te pidan escribir uno nuevo.


3. ¿Qué son las props y por qué son inmutables?

jsx
function Tarjeta({ titulo, descripcion }) {
  // titulo y descripcion NO se pueden modificar acá
  return (
    <div>
      <h2>{titulo}</h2>
      <p>{descripcion}</p>
    </div>
  );
}

// Uso
<Tarjeta titulo="React hooks" descripcion="Cambiaron todo en 2019" />

Las props son el contrato entre padre e hijo. Si el hijo pudiera mutarlas, el flujo de datos se volvería impredecible. El padre es el único responsable de cambiarlas (pasando nuevas props).


4. ¿Para qué sirve la prop `key` en listas?

jsx
// MAL — React no puede reconciliar eficientemente
users.map(u => <li>{u.nombre}</li>)

// BIEN
users.map(u => <li key={u.id}>{u.nombre}</li>)

// EVITAR — usar el índice si la lista puede reordenarse
users.map((u, i) => <li key={i}>{u.nombre}</li>)

key le dice al algoritmo de reconciliación de React qué elemento es qué cuando la lista cambia. Sin key estable, React puede reutilizar el DOM incorrecto y generar bugs visuales raros — especialmente con inputs o animaciones.


5. ¿Qué hace `useState`? Mostrá un ejemplo con counter.

jsx
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0); // valor inicial: 0

  return (
    <div>
      <p>Clicks: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(prev => prev - 1)}>-1</button>
    </div>
  );
}

Nota importante: cuando el próximo estado depende del anterior, usá la forma funcional setCount(prev => prev - 1). Si usás setCount(count - 1) dentro de un closure viejo, podés leer un valor obsoleto.


6. ¿Qué es el virtual DOM y por qué importa?

React mantiene una representación ligera del DOM en memoria (el virtual DOM). Cuando el estado cambia, React:

  1. 1Genera un nuevo virtual DOM
  2. 2Lo compara con el anterior (diffing)
  3. 3Aplica solo los cambios reales al DOM del browser (reconciliación)

Esto es más eficiente que reescribir todo el DOM real en cada cambio. No significa que sea siempre la solución más rápida — herramientas como Svelte no usan virtual DOM — pero en la práctica React es lo suficientemente rápido para la mayoría de los casos.


7. ¿Cómo se maneja un formulario controlado?

jsx
import { useState } from "react";

function Formulario() {
  const [email, setEmail] = useState("");

  function handleSubmit(e) {
    e.preventDefault();
    console.log("Email enviado:", email);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email} // React controla el valor
        onChange={e => setEmail(e.target.value)}
      />
      <button type="submit">Enviar</button>
    </form>
  );
}

En un formulario controlado, el estado de React es la "fuente de verdad". La alternativa (no controlado) usa useRef para leer el valor solo cuando lo necesitás — útil en formularios simples o cuando integrás librerías externas.


8. ¿Qué es el prop drilling y cuándo se vuelve un problema?

jsx
// Prop drilling: pasás `user` por 3 niveles solo para llegar a Avatar
function App() {
  const user = { nombre: "Lucía" };
  return <Layout user={user} />;
}

function Layout({ user }) {
  return <Header user={user} />;
}

function Header({ user }) {
  return <Avatar user={user} />;
}

function Avatar({ user }) {
  return <img alt={user.nombre} />;
}

Se vuelve problema cuando tenés más de 2-3 niveles, porque los componentes intermedios reciben props que no usan. Soluciones: Context, Zustand, o composición de componentes.


NIVEL MEDIO — Hooks y efectos

9. Explicá `useEffect` con dependency array.

jsx
import { useState, useEffect } from "react";

function Perfil({ userId }) {
  const [perfil, setPerfil] = useState(null);

  useEffect(() => {
    // Corre cuando userId cambia
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(setPerfil);

    // Función de limpieza — importante en fetches
    return () => { /* cancelar request si el componente se desmonta */ };
  }, [userId]); // <- dependency array

  if (!perfil) return <p>Cargando...</p>;
  return <p>{perfil.nombre}</p>;
}
  • [] = corre solo al montar
  • [userId] = corre al montar y cuando userId cambia
  • Sin array = corre en cada render (casi siempre un bug)

10. ¿Qué es el "stale closure" y cómo lo evitás?

jsx
// BUG: closure viejo captura count = 0
function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1); // count SIEMPRE es 0 acá
    }, 1000);
    return () => clearInterval(id);
  }, []); // dependency array vacío = closure no se actualiza

  return <p>{count}</p>;
}

// FIX: forma funcional — no necesita leer count del closure
useEffect(() => {
  const id = setInterval(() => {
    setCount(prev => prev + 1); // lee el valor actual
  }, 1000);
  return () => clearInterval(id);
}, []);

El stale closure es uno de los bugs más comunes en React. Aparece cuando un efecto o callback "recuerda" un valor viejo porque no está en el array de dependencias.


11. ¿Para qué sirve `useCallback`? ¿Cuándo lo usás?

jsx
import { useState, useCallback } from "react";

function Padre() {
  const [count, setCount] = useState(0);

  // Sin useCallback: nueva referencia en cada render → Hijo se re-renderiza siempre
  // Con useCallback: misma referencia si las deps no cambian
  const handleClick = useCallback(() => {
    console.log("click");
  }, []); // deps vacías = la función nunca cambia

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>+{count}</button>
      <Hijo onAction={handleClick} />
    </div>
  );
}

const Hijo = React.memo(function Hijo({ onAction }) {
  console.log("Hijo renderizó"); // Solo si onAction cambió
  return <button onClick={onAction}>Acción</button>;
});

useCallback solo tiene sentido combinado con React.memo. Solo en componentes que renderizan mucho o reciben funciones como props — no lo agregues por defecto.


12. ¿Qué hace `useMemo`? Ejemplo real.

jsx
import { useMemo } from "react";

function ListaFiltrada({ items, filtro }) {
  // Sin useMemo: filtra en cada render aunque items y filtro no cambien
  const filtrados = useMemo(
    () => items.filter(item => item.nombre.includes(filtro)),
    [items, filtro] // solo recalcula si cambia alguno de estos
  );

  return (
    <ul>
      {filtrados.map(i => <li key={i.id}>{i.nombre}</li>)}
    </ul>
  );
}

Usalo cuando la computación es costosa (filtrar/ordenar listas grandes, transformaciones complejas). Para cálculos simples, el overhead de useMemo es mayor que el beneficio.


13. ¿Cómo funciona `useRef`? ¿Para qué sirve además del DOM?

jsx
import { useRef, useEffect } from "react";

function AutoFocus() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus(); // acceso directo al DOM
  }, []);

  return <input ref={inputRef} />;
}

// También útil para guardar valores que NO deben causar re-render
function Cronometro() {
  const [tiempo, setTiempo] = useState(0);
  const intervalRef = useRef(null); // no re-renderiza cuando cambia

  function start() {
    intervalRef.current = setInterval(() => setTiempo(t => t + 1), 1000);
  }

  function stop() {
    clearInterval(intervalRef.current);
  }

  return <button onClick={start}>Start</button>;
}

useRef devuelve un objeto { current } que persiste entre renders sin causar re-render. Perfecto para IDs de timers, instancias de librerías externas, o valores previos.


14. ¿Cómo guardás el valor previo de un estado?

jsx
import { useState, useRef, useEffect } from "react";

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value; // actualiza DESPUÉS del render
  });
  return ref.current; // devuelve el valor ANTES del render actual
}

function Precio({ valor }) {
  const valorAnterior = usePrevious(valor);
  const subio = valor > valorAnterior;

  return (
    <p style={{ color: subio ? "green" : "red" }}>
      ${valor} (antes: ${valorAnterior})
    </p>
  );
}

15. ¿Qué es `useReducer` y cuándo preferís usarlo sobre `useState`?

jsx
import { useReducer } from "react";

const initialState = { count: 0, loading: false, error: null };

function reducer(state, action) {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, count: state.count + 1 };
    case "SET_LOADING":
      return { ...state, loading: action.payload };
    case "SET_ERROR":
      return { ...state, error: action.payload, loading: false };
    default:
      return state;
  }
}

function Componente() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <button onClick={() => dispatch({ type: "INCREMENT" })}>
      Count: {state.count}
    </button>
  );
}

Preferí useReducer cuando tenés varios sub-valores relacionados, cuando el próximo estado depende del anterior de forma compleja, o cuando querés que la lógica sea testeable independientemente.


16. ¿Cómo funciona Context y cuáles son sus limitaciones?

jsx
import { createContext, useContext, useState } from "react";

const TemaContext = createContext("claro");

function App() {
  const [tema, setTema] = useState("claro");
  return (
    <TemaContext.Provider value={tema}>
      <Layout />
    </TemaContext.Provider>
  );
}

function Boton() {
  const tema = useContext(TemaContext);
  return <button className={tema}>Click</button>;
}

Limitación clave: cualquier cambio en el Provider re-renderiza TODOS los consumidores, aunque solo cambió una parte del contexto. Para estado frecuente, usá Zustand o separá en múltiples contextos.


17. ¿Qué es un custom hook? Escribí uno real.

jsx
import { useState, useEffect } from "react";

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelado = false;

    setLoading(true);
    fetch(url)
      .then(r => r.json())
      .then(d => { if (!cancelado) setData(d); })
      .catch(e => { if (!cancelado) setError(e); })
      .finally(() => { if (!cancelado) setLoading(false); });

    return () => { cancelado = true; };
  }, [url]);

  return { data, loading, error };
}

// Uso
function Usuarios() {
  const { data, loading, error } = useFetch("/api/users");

  if (loading) return <p>Cargando...</p>;
  if (error) return <p>Error: {error.message}</p>;
  return <ul>{data.map(u => <li key={u.id}>{u.nombre}</li>)}</ul>;
}

Los custom hooks extraen lógica stateful y la hacen reutilizable. La regla: si dos componentes comparten lógica de estado, probablemente debería ser un hook.


18. ¿Qué es el reconciliador de React y cómo decide qué re-renderizar?

React usa el algoritmo Fiber para decidir qué actualizar. Las reglas principales:

  1. 1Si el tipo del elemento cambia (
    → ), destruye el árbol y crea uno nuevo
  2. 2Si el tipo es el mismo, actualiza las props
  3. 3Los hijos se comparan por key
jsx
// React destruye y recrea porque el tipo cambió
{mostrar ? <Input /> : <Textarea />}

// React PRESERVA el estado porque el tipo es igual (solo cambian props)
{mostrar ? <Input placeholder="A" /> : <Input placeholder="B" />}

19. ¿Qué diferencia hay entre `useEffect` y `useLayoutEffect`?

jsx
useEffect(() => {
  // Corre DESPUÉS de que el browser pintó la pantalla
  // No bloquea la pintura — preferido por defecto
});

useLayoutEffect(() => {
  // Corre ANTES de que el browser pinte
  // Bloquea la pintura — usar solo si necesitás medir DOM
  const rect = elementRef.current.getBoundingClientRect();
  setTooltipPosition(rect);
});

Uso real de useLayoutEffect: calcular dimensiones del DOM para posicionar tooltips o menús. En el 99% de los casos, useEffect es lo correcto.


20. ¿Cómo hacés data fetching moderno con Suspense?

jsx
import { Suspense } from "react";
import { useSuspenseQuery } from "@tanstack/react-query";

function DatosUsuario({ id }) {
  const { data } = useSuspenseQuery({
    queryKey: ["user", id],
    queryFn: () => fetch(`/api/users/${id}`).then(r => r.json()),
  });
  return <p>{data.nombre}</p>;
}

function App() {
  return (
    <Suspense fallback={<p>Cargando...</p>}>
      <DatosUsuario id={1} />
    </Suspense>
  );
}

NIVEL SEMI-SENIOR — Patrones y performance

21. ¿Cómo optimizás un componente que renderiza demasiado?

jsx
// 1. React.memo para componentes puros
const Item = React.memo(function Item({ nombre, precio }) {
  return <div>{nombre}: ${precio}</div>;
});

// 2. Estabilizá los objetos/arrays que pasás como props
function Lista() {
  // MAL: nuevo objeto en cada render → Item siempre re-renderiza
  return <Item config={{ bold: true }} />;

  // BIEN: misma referencia
  const config = useMemo(() => ({ bold: true }), []);
  return <Item config={config} />;
}

Primero medí con React DevTools Profiler. El 80% de las veces el cuello de botella no está donde creés.


22. ¿Qué es el Error Boundary y cómo se implementa?

jsx
import { Component } from "react";

class ErrorBoundary extends Component {
  state = { hasError: false, error: null };

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, info) {
    console.error("Error capturado:", error, info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      return (
        <p>
          Algo salió mal.{" "}
          <button onClick={() => this.setState({ hasError: false })}>
            Reintentar
          </button>
        </p>
      );
    }
    return this.props.children;
  }
}

// Uso
<ErrorBoundary>
  <ComponenteQuePuedeFallar />
</ErrorBoundary>

Los Error Boundaries NO capturan errores en event handlers, código asíncrono, ni SSR — solo en el renderizado.


23. ¿Cómo implementás code splitting y lazy loading?

jsx
import { lazy, Suspense } from "react";

const Dashboard = lazy(() => import("./pages/Dashboard"));
const Reportes = lazy(() => import("./pages/Reportes"));

function App() {
  return (
    <Suspense fallback={<div>Cargando página...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/reportes" element={<Reportes />} />
      </Routes>
    </Suspense>
  );
}

Con Next.js: next/dynamic hace lo mismo con más control (SSR, loading state, ssr: false para libs que necesitan el browser).


24. ¿Qué es el patrón Compound Component?

jsx
import { createContext, useContext, useState } from "react";

const AccordionContext = createContext();

function Accordion({ children }) {
  const [open, setOpen] = useState(null);
  return (
    <AccordionContext.Provider value={{ open, setOpen }}>
      <div>{children}</div>
    </AccordionContext.Provider>
  );
}

Accordion.Item = function Item({ id, title, children }) {
  const { open, setOpen } = useContext(AccordionContext);
  const isOpen = open === id;
  return (
    <div>
      <button onClick={() => setOpen(isOpen ? null : id)}>{title}</button>
      {isOpen && <div>{children}</div>}
    </div>
  );
};

// Uso declarativo
<Accordion>
  <Accordion.Item id="1" title="Sección 1">Contenido A</Accordion.Item>
  <Accordion.Item id="2" title="Sección 2">Contenido B</Accordion.Item>
</Accordion>

25. ¿Cómo manejás formularios complejos sin librerías?

jsx
import { useReducer } from "react";

function formReducer(state, action) {
  switch (action.type) {
    case "SET_FIELD":
      return { ...state, [action.field]: action.value };
    case "SET_ERROR":
      return { ...state, errors: { ...state.errors, [action.field]: action.msg } };
    case "RESET":
      return initialState;
    default:
      return state;
  }
}

const initialState = { nombre: "", email: "", errors: {} };

function Formulario() {
  const [state, dispatch] = useReducer(formReducer, initialState);

  function handleChange(e) {
    dispatch({ type: "SET_FIELD", field: e.target.name, value: e.target.value });
  }

  return (
    <form onSubmit={e => { e.preventDefault(); submitForm(state); }}>
      <input name="nombre" value={state.nombre} onChange={handleChange} />
      <input name="email" value={state.email} onChange={handleChange} />
      {state.errors.email && <span>{state.errors.email}</span>}
    </form>
  );
}

26. ¿Qué es `forwardRef` y cuándo lo necesitás?

jsx
import { forwardRef } from "react";

const Input = forwardRef(function Input({ label, ...props }, ref) {
  return (
    <label>
      {label}
      <input ref={ref} {...props} />
    </label>
  );
});

// Uso
function Formulario() {
  const inputRef = useRef();

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <Input ref={inputRef} label="Email" type="email" />;
}

Necesitás forwardRef cuando creás una librería de componentes UI y los consumidores necesitan acceso al DOM interno.


27. ¿Cómo manejás race conditions en fetches concurrentes?

jsx
function Busqueda({ query }) {
  const [resultado, setResultado] = useState(null);
  const latestQuery = useRef(query);

  useEffect(() => {
    latestQuery.current = query;

    fetch(`/api/search?q=${query}`)
      .then(r => r.json())
      .then(data => {
        if (query === latestQuery.current) {
          setResultado(data);
        }
      });
  }, [query]);

  return <div>{resultado?.nombre}</div>;
}

La alternativa más limpia: AbortController para cancelar el fetch anterior cuando llega uno nuevo.


28. ¿Cómo probás un componente con React Testing Library?

jsx
import { render, screen, fireEvent } from "@testing-library/react";
import Counter from "./Counter";

test("incrementa el contador al hacer click", () => {
  render(<Counter />);

  const button = screen.getByRole("button", { name: /\+1/i });

  fireEvent.click(button);

  expect(screen.getByText(/Clicks: 1/i)).toBeInTheDocument();
});

Filosofía de RTL: testeá lo que el usuario ve y hace, no los detalles de implementación. Evitá getByTestId — preferí roles y texto.


29. ¿Qué bug tiene este código de useEffect?

jsx
// BUG: loop infinito
function Componente() {
  const [datos, setDatos] = useState([]);

  useEffect(() => {
    fetch("/api/datos")
      .then(r => r.json())
      .then(d => setDatos(d));
  }, [datos]); // <- BUG: datos está en las deps

  return <div>{datos.length}</div>;
}

datos cambia → efecto corre → setDatos cambia datos → efecto corre → loop infinito. Fix: usar [] si solo necesitás fetchear al montar, o la dependencia que realmente dispara el refetch (un ID, un filtro), nunca el resultado.


30. ¿Qué problema tiene este código con async/await?

jsx
// BUG: useEffect callback no puede ser async
useEffect(async () => {
  const data = await fetchData();
  setData(data);
  // la función de limpieza nunca se llama correctamente
}, []);

// FIX: función async adentro
useEffect(() => {
  let cancelado = false;

  async function cargar() {
    const data = await fetchData();
    if (!cancelado) setData(data);
  }

  cargar();
  return () => { cancelado = true; };
}, []);

useEffect espera que el callback devuelva una función de limpieza o undefined. Una función async devuelve una Promise, lo que React ignora y genera warnings en consola.


NIVEL SENIOR — Arquitectura y performance avanzada

31. ¿Qué es la concurrencia en React y qué es `startTransition`?

jsx
import { useState, useTransition } from "react";

function Busqueda() {
  const [input, setInput] = useState("");
  const [resultados, setResultados] = useState([]);
  const [isPending, startTransition] = useTransition();

  function handleChange(e) {
    setInput(e.target.value); // urgente: se actualiza de inmediato

    startTransition(() => {
      // no urgente: React puede interrumpir este render
      setResultados(buscarEn(data, e.target.value));
    });
  }

  return (
    <div>
      <input value={input} onChange={handleChange} />
      {isPending ? <p>Actualizando...</p> : <Lista items={resultados} />}
    </div>
  );
}

32. ¿Cómo manejás el estado global sin Redux?

jsx
import { create } from "zustand";

const useStore = create(set => ({
  usuario: null,
  setUsuario: usuario => set({ usuario }),
  carrito: [],
  agregarAlCarrito: item => set(state => ({
    carrito: [...state.carrito, item]
  })),
}));

// Re-render granular — cada componente lee solo lo que necesita
function Header() {
  const usuario = useStore(state => state.usuario);
  return <p>{usuario?.nombre}</p>;
}

Zustand es la opción dominante en 2026 para proyectos medianos. Jotai para estado atómico granular. Redux Toolkit para equipos grandes con estado muy complejo.


33. ¿Cómo implementás infinite scroll con Intersection Observer?

jsx
function ListaInfinita() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const bottomRef = useRef();

  useEffect(() => {
    fetch(`/api/items?page=${page}`)
      .then(r => r.json())
      .then(data => setItems(prev => [...prev, ...data]));
  }, [page]);

  useEffect(() => {
    const observer = new IntersectionObserver(entries => {
      if (entries[0].isIntersecting) {
        setPage(p => p + 1);
      }
    });

    if (bottomRef.current) observer.observe(bottomRef.current);
    return () => observer.disconnect();
  }, []);

  return (
    <div>
      {items.map(i => <Item key={i.id} data={i} />)}
      <div ref={bottomRef} style={{ height: 1 }} />
    </div>
  );
}

34. ¿Cómo virtualizás una lista de 10.000 items?

jsx
import { FixedSizeList } from "react-window";

const Row = ({ index, style, data }) => (
  <div style={style}>
    {data[index].nombre}
  </div>
);

function ListaGrande({ items }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
      itemData={items}
    >
      {Row}
    </FixedSizeList>
  );
}

Virtualización = solo renderizás los items visibles + un pequeño buffer. Para 10k items es la diferencia entre 16ms y 2000ms de render.


35. ¿Qué son los Server Components y cómo difieren de los Client Components?

jsx
// Server Component (Next.js app router — default)
// Se ejecuta en el servidor, cero JS al cliente
async function ProductoPage({ params }) {
  const producto = await db.productos.findById(params.id);

  return (
    <div>
      <h1>{producto.nombre}</h1>
      <BotonesInteractivos id={producto.id} />
    </div>
  );
}

// Client Component
"use client";
import { useState } from "react";

function BotonesInteractivos({ id }) {
  const [agregado, setAgregado] = useState(false);
  return (
    <button onClick={() => setAgregado(true)}>
      {agregado ? "Agregado ✓" : "Agregar al carrito"}
    </button>
  );
}

Server Components no pueden usar hooks ni event handlers. La arquitectura recomendada: todo Server Component por defecto, convertí a Client solo lo que necesita interactividad.


36. ¿Cómo implementás optimistic updates?

jsx
import { useMutation, useQueryClient } from "@tanstack/react-query";

const { mutate } = useMutation({
  mutationFn: (id) => fetch(`/api/tareas/${id}/completar`, { method: "POST" }),

  onMutate: async (id) => {
    await queryClient.cancelQueries({ queryKey: ["tareas"] });
    const prev = queryClient.getQueryData(["tareas"]);

    // Actualizamos ANTES de que responda el server
    queryClient.setQueryData(["tareas"], old =>
      old.map(t => t.id === id ? { ...t, completada: true } : t)
    );

    return { prev };
  },

  onError: (err, id, context) => {
    // Si falló, revertimos
    queryClient.setQueryData(["tareas"], context.prev);
  },
});

37. Pregunta de diseño: 500 checkboxes — ¿cómo manejás el estado?

jsx
// MAL: array de booleans — cada check puede re-renderizar toda la lista
const [checks, setChecks] = useState(Array(500).fill(false));

// BIEN: Set con los ids seleccionados
const [seleccionados, setSeleccionados] = useState(new Set());

function toggleItem(id) {
  setSeleccionados(prev => {
    const next = new Set(prev);
    next.has(id) ? next.delete(id) : next.add(id);
    return next;
  });
}

// Cada item solo lee su propio estado — React.memo evita cascadas
const Item = React.memo(function Item({ id, nombre, seleccionado, onToggle }) {
  return (
    <label>
      <input
        type="checkbox"
        checked={seleccionado}
        onChange={() => onToggle(id)}
      />
      {nombre}
    </label>
  );
});

38. ¿Cómo implementás un debounce en React?

jsx
function useDebounce(valor, delay) {
  const [debouncedValue, setDebouncedValue] = useState(valor);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(valor), delay);
    return () => clearTimeout(timer); // limpia el timer anterior
  }, [valor, delay]);

  return debouncedValue;
}

function BusquedaInteligente() {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, 300);

  useEffect(() => {
    if (debouncedQuery) buscar(debouncedQuery);
  }, [debouncedQuery]);

  return <input value={query} onChange={e => setQuery(e.target.value)} />;
}

39. ¿Cómo implementás dark mode sin flash (FOUC)?

jsx
// Script inline ANTES de que React hidrate — evita el flash
<script dangerouslySetInnerHTML={{
  __html: `
    const stored = localStorage.getItem("tema");
    const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
    if (stored === "dark" || (!stored && prefersDark)) {
      document.documentElement.classList.add("dark");
    }
  `
}} />

El script corre sincrónicamente antes del primer paint, por eso no hay flash. Si esperás a React, el usuario ve el modo claro por un instante.


40. ¿Qué es hydration y por qué puede causar bugs?

jsx
// BUG: window no existe en el servidor → mismatch → error de hidratación
function Componente() {
  return <p>Ancho: {window.innerWidth}px</p>;
}

// FIX: valor del cliente después de la hidratación
function Componente() {
  const [ancho, setAncho] = useState(null);

  useEffect(() => {
    setAncho(window.innerWidth); // solo corre en el cliente
  }, []);

  return <p>Ancho: {ancho ?? "---"}px</p>;
}

Hydration es cuando React "toma control" del HTML generado en el servidor. Si el HTML del server y el cliente no coinciden, React genera una advertencia y puede tener comportamiento impredecible.


41. ¿Cuándo usás React Query vs SWR vs fetch directo?

| Necesidad | Solución |

|---|---|

| Fetching simple en 1-2 componentes | useEffect + fetch |

| Cache, refetch, optimistic updates | React Query (TanStack Query) |

| Apps Next.js con foco en simplicidad | SWR |

| Server Components (Next.js 13+) | fetch directo en el servidor |

React Query gana en proyectos grandes: cache automático, dedupe de requests, background refetch, invalidación manual.


42. ¿Qué diferencia hay entre Next.js pages router y app router?

jsx
// Pages Router (legado pero estable)
export async function getServerSideProps({ params }) {
  const producto = await getProducto(params.id);
  return { props: { producto } };
}
export default function ProductoPage({ producto }) { ... }

// App Router (moderno — React Server Components)
async function ProductoPage({ params }) {
  const producto = await getProducto(params.id); // async directo
  return <div>{producto.nombre}</div>;
}

App Router habilita Server Components, streaming, layouts anidados y mejor code splitting. Pages Router es más simple y tiene mejor soporte de librerías legacy.


43. ¿Cómo estructurás un proyecto React grande?

src/
  app/              # rutas (Next.js app router)
  components/
    ui/             # componentes genéricos (Button, Input, Modal)
    features/       # componentes de negocio (ProductCard, UserProfile)
  hooks/            # custom hooks reutilizables
  lib/              # utils, clientes API, helpers
  stores/           # estado global (Zustand)
  types/            # tipos TypeScript compartidos

Principio clave: colocá lo que cambia junto. Si UserProfile tiene su propio hook y sus tests, ponelos en la misma carpeta. Evitá components/ como papelera global.


44. ¿Cómo implementás un tooltip con posicionamiento dinámico?

jsx
import { useState, useRef, useLayoutEffect } from "react";

function Tooltip({ children, texto }) {
  const [pos, setPos] = useState({ top: 0, left: 0 });
  const [visible, setVisible] = useState(false);
  const triggerRef = useRef();
  const tooltipRef = useRef();

  useLayoutEffect(() => {
    if (!visible) return;
    const trigger = triggerRef.current.getBoundingClientRect();
    const tooltip = tooltipRef.current.getBoundingClientRect();
    setPos({
      top: trigger.top - tooltip.height - 8,
      left: trigger.left + trigger.width / 2 - tooltip.width / 2,
    });
  }, [visible]);

  return (
    <>
      <span
        ref={triggerRef}
        onMouseEnter={() => setVisible(true)}
        onMouseLeave={() => setVisible(false)}
      >
        {children}
      </span>
      {visible && (
        <div
          ref={tooltipRef}
          style={{ position: "fixed", top: pos.top, left: pos.left }}
        >
          {texto}
        </div>
      )}
    </>
  );
}

useLayoutEffect acá es correcto porque necesitás medir el DOM antes de que el browser pinte, para evitar el flash de tooltip mal posicionado.


45. ¿Cómo compartís tipos entre componente padre e hijo en TypeScript?

tsx
// types.ts — fuente de verdad
export type Usuario = {
  id: number;
  nombre: string;
  email: string;
  rol: "admin" | "user";
};

// UsuarioCard.tsx
type Props = {
  usuario: Usuario;
  onEdit: (id: number) => void;
  compact?: boolean;
};

const UsuarioCard: React.FC<Props> = ({ usuario, onEdit, compact = false }) => {
  return (
    <div className={compact ? "p-2" : "p-4"}>
      <p>{usuario.nombre}</p>
      <button onClick={() => onEdit(usuario.id)}>Editar</button>
    </div>
  );
};

46. ¿Qué es `useInsertionEffect` y para quién es?

jsx
import { useInsertionEffect } from "react";

// Solo para autores de librerías CSS-in-JS (styled-components, Emotion)
// Corre ANTES de useLayoutEffect y useEffect
// Sirve para insertar estilos al DOM antes de que el browser pinte
useInsertionEffect(() => {
  const style = document.createElement("style");
  style.textContent = `.btn { color: red }`;
  document.head.appendChild(style);
  return () => document.head.removeChild(style);
}, []);

No lo usés en código de aplicación normal. Es la escape hatch para autores de librerías de estilos en tiempo de ejecución.


47. ¿Cómo manejás el patrón Provider con TypeScript estricto?

tsx
import { createContext, useContext, useState, ReactNode } from "react";

type AuthContextType = {
  user: Usuario | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
};

// undefined como valor inicial — fuerza a verificar que el hook se use dentro del Provider
const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<Usuario | null>(null);

  async function login(email: string, password: string) {
    const u = await authApi.login(email, password);
    setUser(u);
  }

  return (
    <AuthContext.Provider value={{ user, login, logout: () => setUser(null) }}>
      {children}
    </AuthContext.Provider>
  );
}

// Hook con guardrail — lanza si se usa fuera del Provider
export function useAuth(): AuthContextType {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error("useAuth debe usarse dentro de AuthProvider");
  return ctx;
}

48. ¿Cómo evitás re-renders en una lista con contexto?

jsx
// MAL: el contexto tiene todo el estado → todos los consumidores re-renderizan
const AppContext = createContext();
// value={{ user, carrito, tema, notificaciones }} — cualquier cambio = re-render global

// BIEN: contextos separados por frecuencia de cambio
const UserContext = createContext();      // cambia poco (login/logout)
const CarritoContext = createContext();   // cambia frecuente
const TemaContext = createContext();      // cambia raramente

// O usá Zustand con selectores granulares
const useUser = () => useStore(state => state.user);     // solo re-renderiza si user cambia
const useCarrito = () => useStore(state => state.carrito); // independiente

49. ¿Cómo implementás un portal para modales?

jsx
import { createPortal } from "react-dom";
import { useEffect, useRef } from "react";

function Modal({ isOpen, onClose, children }) {
  const elRef = useRef(document.createElement("div"));

  useEffect(() => {
    const el = elRef.current;
    document.body.appendChild(el);
    return () => document.body.removeChild(el);
  }, []);

  useEffect(() => {
    if (isOpen) {
      document.body.style.overflow = "hidden"; // bloquea scroll
    }
    return () => { document.body.style.overflow = ""; };
  }, [isOpen]);

  if (!isOpen) return null;

  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={e => e.stopPropagation()}>
        {children}
      </div>
    </div>,
    elRef.current
  );
}

El portal renderiza el modal fuera del árbol del componente padre — en document.body — lo que evita problemas de overflow: hidden o z-index del padre.


50. ¿Qué es `useImperativeHandle` y cuándo lo usás?

jsx
import { forwardRef, useImperativeHandle, useRef } from "react";

const VideoPlayer = forwardRef(function VideoPlayer(props, ref) {
  const videoRef = useRef();

  // Exponés solo la API que querés — no todo el elemento DOM
  useImperativeHandle(ref, () => ({
    play: () => videoRef.current.play(),
    pause: () => videoRef.current.pause(),
    seek: (time) => { videoRef.current.currentTime = time; },
  }));

  return <video ref={videoRef} src={props.src} />;
});

// Uso — el padre solo puede play/pause/seek, no accede al DOM directamente
function Pantalla() {
  const playerRef = useRef();

  return (
    <>
      <VideoPlayer ref={playerRef} src="/video.mp4" />
      <button onClick={() => playerRef.current.play()}>Reproducir</button>
    </>
  );
}

Cómo practicar estas preguntas antes de tu entrevista

Leer el código no es lo mismo que escribirlo. Las entrevistas técnicas de React te piden:

  1. 1Explicar en voz alta mientras escribís — practicá decir "lo que estoy haciendo es..." mientras codificás
  2. 2Identificar el problema antes de escribir — tomá 30 segundos para pensar, el entrevistador aprecia el proceso
  3. 3Conocer los trade-offs — no existe "la respuesta correcta", existe "depende de" seguido de razonamiento real
  4. 4Preguntar antes de asumir — "¿qué tamaño tiene la lista?" "¿qué navegadores soportamos?" demuestra madurez técnica

Los temas que más caen por nivel:

  • Junior: JSX, props vs state, useState, useEffect básico, keys en listas
  • Semi-senior: useCallback/useMemo, custom hooks, Context, testing con RTL
  • Senior: performance, arquitectura, Server Components, optimistic updates, concurrencia

Practicá escribir código sin autocompletado. Muchas entrevistas usan editores sin IntelliSense — y eso es exactamente donde falla el que solo copió sin entender.

FAQ

¿Cuáles son las preguntas más comunes en entrevistas de React para junior?+

Las más frecuentes: qué es JSX, diferencia entre props y estado, cómo funciona useState, para qué sirve la prop key en listas, y el ciclo de vida con useEffect. Si las respondés con código real y explicás el porqué, ya superás al 70% de los candidatos junior.

¿Qué diferencia hay entre useCallback y useMemo?+

useMemo memoriza el resultado de una función costosa. useCallback memoriza la referencia de la función en sí. Ambos solo tienen sentido combinados con React.memo para evitar re-renders de componentes hijos — sin eso, el overhead supera el beneficio.

¿Qué es un stale closure en React y cómo lo evitás?+

Un stale closure ocurre cuando un efecto o callback captura un valor viejo de una variable de estado porque ese valor no estaba en el dependency array. Se evita usando la forma funcional del setter — setCount(prev => prev + 1) — o agregando la variable a las dependencias del efecto.

¿Qué son los React Server Components?+

Componentes que se ejecutan en el servidor, generan HTML, y envían cero JavaScript al cliente. No pueden usar useState, useEffect ni event handlers. En Next.js App Router son el default. Se combinan con Client Components — marcados con use client — para las partes interactivas.

¿Cuándo usás useReducer en lugar de useState?+

useReducer es preferible cuando tenés múltiples valores de estado relacionados que cambian juntos, cuando el próximo estado depende del anterior de forma compleja, o cuando querés extraer la lógica de estado a una función pura que sea fácil de testear por separado.

¿Cómo se evita el re-render innecesario de componentes hijos en React?+

Con React.memo para el componente hijo, useCallback para estabilizar las funciones que se pasan como props, y useMemo para estabilizar objetos o arrays. Sin embargo, primero medí con React DevTools Profiler — el 80% de las veces el problema no está donde creés.

¿Qué es hydration en React y por qué puede causar bugs?+

Hydration es cuando React toma control del HTML generado en el servidor y lo hace interactivo en el cliente. Si el HTML del servidor y el cliente no coinciden — por ejemplo, porque usaste window o Date.now() directamente — React genera una advertencia y puede tener comportamiento impredecible.

¿Para qué sirve forwardRef en React?+

forwardRef permite que un componente funcional exponga una ref al componente padre. Es necesario cuando construís una librería de componentes UI y los consumidores necesitan acceso al elemento DOM interno — por ejemplo, para hacer focus() o medir dimensiones.

Related articles

Cómo usar el método STAR en entrevistas (con ejemplos reales)

Aprende a responder preguntas difíciles usando el método STAR en entrevistas. Consejos y ejemplos concretos para roles remotos tech de LATAM.

Cómo conseguir trabajo remoto en dólares desde LATAM: guía real

Descubre consejos concretos para conseguir trabajo remoto en dólares desde LATAM: estrategias de búsqueda, preparación y entrevista para roles tecnológicos.

Las mejores preguntas para hacerle al entrevistador al final

Descubre las mejores preguntas para hacerle al entrevistador al final, útiles para entrevistas tech remotas, diferenciándote y logrando roles en dólares.

Cómo preparar entrevistas de desarrollo sin experiencia previa

Consejos prácticos para enfrentar entrevistas de tu primer trabajo como desarrollador, incluso sin experiencia. Técnicas para destacar y convencer en cada etapa.

Prepare for your real interview

Paste your job link: we research who's interviewing you and rehearse you live.

Start free →

Have an interview coming up? Install the live copilot →

InterviewHack.ai

Prepare for the exact interview: who's interviewing you, a tailored CV, and a real coach.

Product

JobsFree ATS checkerInterview-English checkSalary checkLATAM salary reportFree coursesBlogTailored CVSpoken practiceIt's free

Remote jobs

ReactPythonFull-StackLATAMArgentinaMexicoSee all →

Prepare

Spoken practiceFrontendBackendAI EngineerBy companySell with your CV

Company

For employersAboutContactPrivacyTerms

© 2026 InterviewHack.ai · Your CV is yours. Never used to train anything. · A product of IA-PTY