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

Más de 50 preguntas reales de entrevistas de React con código, respuestas explicadas y patrones comunes. Desde hooks hasta performance — preparate de verdad.

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 en el estado
  • El próximo estado depende del anterior de forma compleja
  • Querés que la lógica de estado sea testeable de forma independiente

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

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

// 1. Crear el contexto
const TemaContext = createContext("claro");

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

// 3. Consumir desde cualquier nivel
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 (ej: posición del mouse), usá Zustand o separá en múltiples contextos.


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

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

// Hook reutilizable para fetch con loading/error
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; }; // cleanup
  }, [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 en React moderno con Suspense?

jsx
import { Suspense } from "react";

// Con React Query (la forma recomendada en 2026)
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>;
}

// El padre maneja el loading state
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
// Diagnóstico: React DevTools Profiler — tab "Profiler" → Record

// 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} />;
}

// 3. Separá el estado para que los cambios afecten solo lo necesario
// Estado global que cambia mucho → Zustand o atom granular (Jotai)

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

jsx
import { Component } from "react";

// Error Boundaries solo se pueden escribir como clase (todavía en 2026)
class ErrorBoundary extends Component {
  state = { hasError: false, error: null };

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

  componentDidCatch(error, info) {
    // Loggear a Sentry, Datadog, etc.
    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, o SSR — solo en el renderizado.


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

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

// El componente se carga solo cuando se necesita (lazy)
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
// Compound Components: subcomponentes que comparten estado implícito
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 — la API es declarativa y composable
<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 });
  }

  function validate() {
    if (!state.email.includes("@")) {
      dispatch({ type: "SET_ERROR", field: "email", msg: "Email inválido" });
      return false;
    }
    return true;
  }

  return (
    <form onSubmit={e => { e.preventDefault(); if (validate()) 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";

// Los componentes funcionales no exponen refs por defecto
// forwardRef permite "pasar" una ref hacia adentro
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 múltiples estados asíncronos sin race conditions?

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

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 => {
        // Solo actualizamos si este resultado es del query más reciente
        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
// Counter.test.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 });
  const count = screen.getByText(/Clicks: 0/i);

  fireEvent.click(button);

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

test("decrementa con el botón -1", () => {
  render(<Counter initialValue={5} />);
  fireEvent.click(screen.getByRole("button", { name: /-1/i }));
  expect(screen.getByText(/Clicks: 4/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. ¿Cómo mockéas un hook en tests?

jsx
// useFetch.test.jsx
import { render, screen } from "@testing-library/react";
import * as hooks from "../hooks/useFetch";
import Usuarios from "./Usuarios";

test("muestra usuarios cuando carga", () => {
  // Mock del hook
  jest.spyOn(hooks, "useFetch").mockReturnValue({
    data: [{ id: 1, nombre: "Lucía" }],
    loading: false,
    error: null,
  });

  render(<Usuarios />);
  expect(screen.getByText("Lucía")).toBeInTheDocument();
});

test("muestra spinner mientras carga", () => {
  jest.spyOn(hooks, "useFetch").mockReturnValue({
    data: null,
    loading: true,
    error: null,
  });

  render(<Usuarios />);
  expect(screen.getByText(/Cargando/i)).toBeInTheDocument();
});

30. ¿Qué es el patrón Render Props y cuándo lo reemplazaron los hooks?

jsx
// Render Props (pre-hooks) — pasás una función como prop
class MouseTracker extends Component {
  state = { x: 0, y: 0 };

  handleMove = e => this.setState({ x: e.clientX, y: e.clientY });

  render() {
    return (
      <div onMouseMove={this.handleMove}>
        {this.props.render(this.state)} {/* llamás la función */}
      </div>
    );
  }
}

// Uso
<MouseTracker render={({ x, y }) => <p>Mouse en {x}, {y}</p>} />

// Equivalente moderno con hook
function useMouse() {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const move = e => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener("mousemove", move);
    return () => window.removeEventListener("mousemove", move);
  }, []);
  return pos;
}

Los hooks reemplazaron Render Props y HOCs para compartir lógica stateful. Render Props todavía aparece en algunas APIs de librerías (Formik, React Router).


NIVEL SENIOR — Arquitectura y performance avanzada

31. ¿Qué es React.memo y cuándo NO lo usás?

jsx
// React.memo hace shallow comparison de props
const Boton = React.memo(function Boton({ onClick, label }) {
  console.log("Boton renderizó");
  return <button onClick={onClick}>{label}</button>;
});

// PROBLEMA: onClick es una función nueva en cada render de Padre
function Padre() {
  const [count, setCount] = useState(0);

  // Sin useCallback: React.memo no ayuda porque onClick cambia siempre
  const handleClick = useCallback(() => console.log("click"), []);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
      <Boton onClick={handleClick} label="Acción" />
    </div>
  );
}

No uses React.memo si:

  • El componente es simple (el overhead de comparación supera el render)
  • Las props cambian en casi todos los renders de todas formas
  • El componente no tiene renders costosos

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

jsx
import { useState, startTransition, 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/pausar este render
      // si llega una actualización más urgente
      setResultados(buscarEn(data, e.target.value));
    });
  }

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

Concurrencia significa que React puede trabajar en múltiples tareas de UI al mismo tiempo, priorizando las urgentes (input del usuario) sobre las pesadas (renderizar 1000 items).


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

jsx
// Zustand — la opción más popular en 2026 para proyectos medianos
import { create } from "zustand";

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

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

function Carrito() {
  const { carrito, agregarAlCarrito } = useStore();
  return <p>{carrito.length} items</p>;
}

34. ¿Qué es el patrón Container/Presentational?

jsx
// Container: maneja datos y lógica
function UsuariosContainer() {
  const { data: usuarios, loading } = useFetch("/api/users");

  if (loading) return <Spinner />;
  return <UsuariosList usuarios={usuarios} />;
}

// Presentational: solo renderiza, sin lógica de datos
function UsuariosList({ usuarios }) {
  return (
    <ul>
      {usuarios.map(u => (
        <li key={u.id}>{u.nombre} — {u.email}</li>
      ))}
    </ul>
  );
}

Este patrón sigue siendo válido aunque sea anterior a los hooks. Separa claramente la lógica de la presentación y hace los componentes presentacionales trivialmente testeables.


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

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

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>
  );
}

36. ¿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} // altura de cada fila en px
      width="100%"
      itemData={items}
    >
      {Row}
    </FixedSizeList>
  );
}

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


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

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

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

// Client Component — tiene estado e interactividad
"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, event handlers ni APIs del browser. Client Components son los de siempre. La arquitectura recomendada: empezá todo como Server Component, convertí a Client solo lo que necesita interactividad.


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

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

function ListaTareas() {
  const queryClient = useQueryClient();

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

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

      // Guardamos el estado anterior (para rollback)
      const prev = queryClient.getQueryData(["tareas"]);

      // Actualizamos optimistamente 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);
    },
  });

  return <button onClick={() => mutate(tareaId)}>Completar</button>;
}

39. ¿Cómo medís y mejorás el First Contentful Paint en una app React?

Métricas clave (Lighthouse / Web Vitals):

  • LCP (Largest Contentful Paint): < 2.5s
  • FID / INP (Interaction): < 200ms
  • CLS (Layout Shift): < 0.1

Técnicas en React:

jsx
// 1. Lazy loading de rutas (ya visto)
const Reportes = lazy(() => import("./Reportes"));

// 2. Preload de recursos críticos
<link rel="preload" href="/fonts/inter.woff2" as="font" crossOrigin="" />

// 3. Imágenes con dimensiones explícitas para evitar CLS
<img src="hero.jpg" width={1280} height={720} alt="..." />

// 4. Skeleton screens en lugar de spinners
function PostSkeleton() {
  return (
    <div className="animate-pulse">
      <div className="h-4 bg-gray-200 rounded w-3/4 mb-2" />
      <div className="h-4 bg-gray-200 rounded w-1/2" />
    </div>
  );
}

40. ¿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

Principios: colocá lo que cambia junto. Si UserProfile tiene su propio hook y sus tests, ponelos en la misma carpeta. Evitá components/ como papelera global — separalo por feature o dominio.


PREGUNTAS DE DEBUGGING Y CASOS REALES

41. ¿Qué bug tiene este código?

jsx
function Lista({ items }) {
  const [seleccionado, setSeleccionado] = useState(items[0]);

  return (
    <ul>
      {items.map(item => (
        <li
          key={item.id}
          onClick={() => setSeleccionado(item)}
          style={{ fontWeight: seleccionado === item ? "bold" : "normal" }}
        >
          {item.nombre}
        </li>
      ))}
    </ul>
  );
}

Bug: si items cambia (el padre pasa una nueva lista), seleccionado sigue siendo el objeto del array anterior. La comparación por referencia seleccionado === item siempre es false.

Fix:

jsx
const [seleccionadoId, setSeleccionadoId] = useState(items[0]?.id);
// compará por id, no por referencia
style={{ fontWeight: seleccionadoId === item.id ? "bold" : "normal" }}

42. ¿Por qué este useEffect corre en loop infinito?

jsx
function Componente() {
  const [datos, setDatos] = useState([]);

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

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

Diagnóstico: datos cambia → efecto corre → setDatos cambia datos → efecto corre → loop.

Fix: si solo necesitás fetchear al montar, usá []. Si necesitás re-fetchear cuando algo cambia, usá la dependencia que dispara el fetch (un ID, un filtro), no el resultado.


43. ¿Qué problema tiene este componente con async/await?

jsx
// BUG: useEffect callback no puede ser async directamente
useEffect(async () => {
  const data = await fetchData();
  setData(data);
  // la función de limpieza (return) 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.


44. ¿Cómo manejás eventos en listas sin crear una función por item?

jsx
// MAL: nueva función por cada item en cada render
{items.map(item => (
  <button key={item.id} onClick={() => handleClick(item.id)}>
    {item.nombre}
  </button>
))}

// MEJOR para listas muy grandes: event delegation
function Lista({ items }) {
  function handleClick(e) {
    const id = e.target.dataset.id;
    if (id) procesarItem(id);
  }

  return (
    <ul onClick={handleClick}>
      {items.map(item => (
        <li key={item.id} data-id={item.id}>{item.nombre}</li>
      ))}
    </ul>
  );
}

En la práctica, las funciones inline son aceptables para listas pequeñas. El event delegation importa cuando tenés miles de items.


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>
  );
};

PREGUNTAS SOBRE REACT Y EL ECOSISTEMA

46. ¿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.


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

jsx
// Pages Router (legado pero estable)
// pages/productos/[id].tsx
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)
// app/productos/[id]/page.tsx
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.


48. ¿Cómo manejás la internacionalización (i18n) en React?

jsx
// next-intl (popular en 2026 con App Router)
import { useTranslations } from "next-intl";

function Header() {
  const t = useTranslations("Header");
  return <h1>{t("titulo")}</h1>;
}

// messages/es.json
// { "Header": { "titulo": "Bienvenido" } }

// messages/en.json
// { "Header": { "titulo": "Welcome" } }

En proyectos LATAM, considerá: voseo vs tuteo, fechas en formato DD/MM/YYYY, y monedas locales (Intl.NumberFormat).


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

jsx
// _document.tsx (Next.js pages router)
// 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");
    }
  `
}} />

// Hook para cambiar el tema
function useTema() {
  const [tema, setTema] = useState(() =>
    typeof window !== "undefined"
      ? localStorage.getItem("tema") || "system"
      : "system"
  );

  function toggleTema() {
    const nuevo = tema === "dark" ? "light" : "dark";
    setTema(nuevo);
    localStorage.setItem("tema", nuevo);
    document.documentElement.classList.toggle("dark", nuevo === "dark");
  }

  return { tema, toggleTema };
}

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

jsx
// Hydration: React "toma control" del HTML generado en el servidor
// Si el HTML del servidor y el cliente no coinciden → error

// BUG CLÁSICO: código que solo existe en el browser
function Componente() {
  // window no existe en el servidor → mismatch → error de hidratación
  return <p>Ancho: {window.innerWidth}px</p>;
}

// FIX: montar el 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>;
}

51. ¿Qué diferencia hay entre `useEffect` y `useInsertionEffect`?

jsx
// useInsertionEffect — solo para librerías de CSS-in-JS
// Corre ANTES de useLayoutEffect y useEffect
// Sirve para insertar estilos al DOM antes de que el browser pinte

import { useInsertionEffect } from "react";

// No lo usés en código de aplicación normal
// Es la escape hatch para autores de styled-components, Emotion, etc.
useInsertionEffect(() => {
  const style = document.createElement("style");
  style.textContent = `.btn { color: red }`;
  document.head.appendChild(style);
  return () => document.head.removeChild(style);
}, []);

52. Pregunta de diseño: tenés una lista de 500 checkboxes — ¿cómo manejás el estado?

jsx
// MAL: array de booleans — cada check re-renderiza 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
const Item = React.memo(function Item({ id, nombre, seleccionado, onToggle }) {
  return (
    <label>
      <input
        type="checkbox"
        checked={seleccionado}
        onChange={() => onToggle(id)}
      />
      {nombre}
    </label>
  );
});

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

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

// Custom hook de debounce
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;
}

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

  useEffect(() => {
    if (debouncedQuery) buscar(debouncedQuery);
  }, [debouncedQuery]); // solo fetcha cuando el usuario dejó de escribir

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

54. ¿Cuál es la diferencia entre controlled y uncontrolled components? ¿Cuándo usás `useRef` para forms?

jsx
// Controlled: React controla el valor
function FormControlado() {
  const [nombre, setNombre] = useState("");
  return <input value={nombre} onChange={e => setNombre(e.target.value)} />;
}

// Uncontrolled: el DOM controla el valor, React solo lo lee cuando lo necesita
function FormNoControlado() {
  const inputRef = useRef();

  function handleSubmit() {
    console.log(inputRef.current.value); // lee al enviar
  }

  return (
    <form onSubmit={handleSubmit}>
      <input ref={inputRef} defaultValue="inicial" />
    </form>
  );
}

Uncontrolled es útil cuando integrás con librerías que manejan el DOM directamente, o en formularios muy simples donde no necesitás validación en tiempo real.


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.

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.

Related articles

Cómo responder 'háblame de vos' en una entrevista técnica

Descubre cómo abordar la pregunta ‘háblame de vos’ en entrevistas técnicas para roles remotos en tecnología, con ejemplos y consejos útiles.

Cómo preparar una entrevista técnica en inglés siendo de LATAM

Aprende a preparar una entrevista técnica en inglés desde LATAM. Consejos reales para superar los desafíos y destacar en roles remotos en dólares.

Cómo prepararte para una entrevista de Desarrollador Frontend

Descubre consejos prácticos para preparar tu entrevista de Desarrollador Frontend. Domina técnicas, retos y preguntas en roles remotos y en inglés.

Cómo conseguir trabajo remoto de React en dólares desde LATAM

Guía práctica para conseguir trabajos remotos de React pagando en dólares desde LATAM: estrategias, CV, portfolio, entrevistas y plataformas clave.

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