Skip to content

Top 50 Preguntas Frecuentes de React

Guía completa con las preguntas que aparecen en la mayoría de entrevistas técnicas.


Definición profesional: Extensión de sintaxis para JavaScript que permite escribir estructura similar a HTML directamente en JS. Babel lo transpila a llamadas React.createElement().

JSX básico
// JSX
const element = <h1 className="titulo">Hola</h1>;
// Equivalente compilado
const element = React.createElement('h1', { className: 'titulo' }, 'Hola');
// JSX soporta expresiones JS
const nombre = "Juan";
const greeting = <h1>Hola, {nombre.toUpperCase()}</h1>;

Cómo responder en entrevista:

“JSX es azúcar sintáctico sobre React.createElement(). No es HTML sino JavaScript: usa className en vez de class, htmlFor en vez de for, y todos los atributos son camelCase. Las llaves {} permiten insertar cualquier expresión JavaScript. Se compila en tiempo de build, no en runtime.”


2. ¿Por qué usamos className en vez de class?

Section titled “2. ¿Por qué usamos className en vez de class?”

Definición profesional: class es una palabra reservada de JavaScript (para definir clases). JSX al ser JavaScript usa className para el atributo CSS y htmlFor para el atributo for de los labels.

className y htmlFor en JSX
// HTML
<div class="contenedor">
<label for="email">Email</label>
</div>
// JSX equivalente
<div className="contenedor">
<label htmlFor="email">Email</label>
</div>

Cómo responder en entrevista:

“JSX se compila a JavaScript, donde class es una palabra reservada para definir clases. React usa className para evitar el conflicto. Similarmente, for en labels se convierte en htmlFor. Estos son los dos casos más comunes donde JSX difiere del HTML estándar.”


Definición profesional: Componente especial <React.Fragment> (o <>) que agrupa múltiples elementos sin añadir nodos extra al DOM. Útil cuando un componente debe retornar varios elementos sin un wrapper innecesario.

Fragments
// Sin Fragment - agrega un <div> innecesario al DOM
function Ejemplo() {
return (
<div>
<h1>Título</h1>
<p>Párrafo</p>
</div>
);
}
// Con Fragment - sin nodo extra en el DOM
function Ejemplo() {
return (
<>
<h1>Título</h1>
<p>Párrafo</p>
</>
);
}

Cómo responder en entrevista:

“Los Fragments resuelven que React requiere un único elemento raíz sin agregar divs inútiles al DOM. La sintaxis corta <> es preferible. Uso <React.Fragment key={id}> (con key) cuando necesito iterar sobre ellos en listas.”


Definición profesional: Técnicas para renderizar JSX condicionalmente según el estado o props: operador &&, ternario ? :, return temprano, o variable auxiliar.

Renderizado condicional
function Componente({ isLoggedIn }) {
// Return temprano - para casos muy diferentes
if (!isLoggedIn) return <Login />;
return (
<div>
{/* && - cuando solo hay un caso positivo */}
{isLoggedIn && <UserPanel />}
{/* Ternario - cuando hay dos alternativas */}
{isLoggedIn ? <UserPanel /> : <Login />}
</div>
);
}

Cómo responder en entrevista:

“Prefiero el return temprano para condiciones que cambian completamente el componente. El operador && para mostrar/ocultar algo. El ternario para alternar entre dos opciones. Cuidado con &&: si la condición es 0, React renderiza 0. Mejor usar Boolean(condition) && o ternario.”


Definición profesional: Con .map() transformando arrays en elementos JSX. Cada elemento requiere una prop key única y estable para que React identifique cambios eficientemente.

Renderizar listas
function Lista({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.nombre}</li>
))}
</ul>
);
}
// NO usar índice como key - causa bugs con reordenamiento
// {items.map((item, i) => <li key={i}>{item.nombre}</li>)}

Cómo responder en entrevista:

“Uso .map() y cada elemento necesita key única y estable. La key le dice a React qué elementos cambiaron, se agregaron o eliminaron. Nunca uso el índice como key: si reordeno la lista, React reutiliza nodos incorrectamente causando bugs de estado. Prefiero IDs de la base de datos.”


6. ¿Qué es prop drilling y cómo evitarlo?

Section titled “6. ¿Qué es prop drilling y cómo evitarlo?”

Definición profesional: Anti-patrón donde los props pasan por múltiples componentes intermedios que no los usan, solo para hacer llegar datos a un componente profundo.

Prop drilling
// Prop drilling - componentes intermedios no usan user
<App user={user}>
<Layout user={user}>
<Sidebar user={user}>
<UserMenu user={user} /> {/* Solo este lo usa */}
</Sidebar>
</Layout>
</App>
// Solución: Context API
const UserContext = createContext();
// Proveer en App, consumir directamente en UserMenu

Cómo responder en entrevista:

“Prop drilling hace el código frágil: cambiar el tipo de dato requiere modificar todos los componentes intermedios. Las soluciones son: Context API (para datos globales como tema, usuario), estado global con Zustand/Redux, o composición de componentes (pasar elementos ya construidos como children o props).“


Definición profesional: Sistema de React para compartir datos entre componentes sin prop drilling. Basado en tres piezas: createContext, Provider y useContext.

Context API
const UserContext = createContext(null);
// Provider - envuelve los componentes que necesitan el dato
function App() {
const [user, setUser] = useState({ nombre: 'Juan' });
return (
<UserContext.Provider value={{ user, setUser }}>
<Router />
</UserContext.Provider>
);
}
// Consumer - cualquier nivel sin prop drilling
function UserMenu() {
const { user } = useContext(UserContext);
return <span>Hola, {user.nombre}</span>;
}

Cómo responder en entrevista:

“Context API permite compartir datos sin prop drilling. Crea el contexto con createContext, envuelve con Provider y consume con useContext. El problema: cuando el valor del Provider cambia, TODOS los consumidores se re-renderizan. Para estado con muchas actualizaciones prefiero Zustand o Redux.”


Definición profesional: Context es nativo pero provoca re-renders globales. Redux (y Zustand) ofrecen suscripciones selectivas, middleware y DevTools.

ContextRedux/Zustand
Estado simpleEstado complejo
Pocas actualizacionesActualizaciones frecuentes
Sin middlewareNecesitas middleware
Built-inDependencia extra

Cómo responder en entrevista:

“Uso Context para datos que cambian poco: tema, locale, usuario autenticado. Para estado con actualizaciones frecuentes (carrito, UI state) prefiero Zustand: más ligero que Redux, sin boilerplate. Redux sigue siendo válido para apps grandes que necesitan DevTools avanzados y middleware.”


Definición profesional: Hook que retorna un objeto mutable { current } que persiste entre renders. Cambiar .current no provoca re-render. Tiene dos usos principales: acceder al DOM y almacenar valores mutables sin causar renders.

useRef
function Formulario() {
const inputRef = useRef(null);
const renderCount = useRef(0);
useEffect(() => {
inputRef.current.focus(); // Acceso directo al DOM
renderCount.current++; // Contador sin re-render
});
return <input ref={inputRef} />;
}

Cómo responder en entrevista:

useRef tiene dos casos: acceso al DOM (focus, scroll, medidas) y valores mutables que no deben causar re-render (timers, contadores de render, valores previos). La diferencia con useState: cambiar .current no re-renderiza. Para guardar el valor anterior de una prop, uso useRef en un useEffect.”


Definición profesional: Hook que memoriza una función, retornando la misma referencia entre renders mientras sus dependencias no cambien. Evita que componentes hijos con React.memo se re-rendericen por recibir una nueva referencia de función.

useCallback
const Boton = React.memo(({ onClick, texto }) => {
console.log('Boton renderizado');
return <button onClick={onClick}>{texto}</button>;
});
function Formulario() {
const [nombre, setNombre] = useState('');
// Sin useCallback: nueva función cada render → Boton se re-renderiza
// Con useCallback: misma función → Boton NO se re-renderiza al escribir
const handleSubmit = useCallback(() => {
console.log('Enviado:', nombre);
}, [nombre]);
return (
<>
<input value={nombre} onChange={e => setNombre(e.target.value)} />
<Boton onClick={handleSubmit} texto="Enviar" />
</>
);
}

Cómo responder en entrevista:

useCallback memoriza la referencia de una función. Úsalo cuando pasas funciones a componentes con React.memo o cuando una función es dependencia de useEffect. Sin él, cada render crea una nueva función, rompiendo la memoización. useCallback(fn, deps) equivale a useMemo(() => fn, deps).”


Definición profesional: Hook que memoriza el resultado de un cálculo costoso, recomputándolo solo cuando sus dependencias cambian.

useMemo
function ListaUsuarios({ usuarios, filtro }) {
// Sin useMemo: filtra en CADA render aunque filtro no cambie
// Con useMemo: solo filtra cuando usuarios o filtro cambian
const usuariosFiltrados = useMemo(() => {
return usuarios.filter(user =>
user.nombre.toLowerCase().includes(filtro.toLowerCase())
);
}, [usuarios, filtro]);
return (
<ul>
{usuariosFiltrados.map(user => (
<li key={user.id}>{user.nombre}</li>
))}
</ul>
);
}

Cómo responder en entrevista:

useMemo memoriza el valor retornado por una función. Úsalo para cálculos costosos (filtrar listas grandes, transformaciones de datos) o para crear objetos que serán props de componentes memorizados. No abusar: tiene overhead propio. React puede descartar el caché en casos especiales (optimización de memoria).“


12. ¿Cuál es la diferencia entre useMemo y useCallback?

Section titled “12. ¿Cuál es la diferencia entre useMemo y useCallback?”

Definición profesional: useMemo memoriza un valor (resultado de ejecutar la función). useCallback memoriza la función en sí misma.

useMemo vs useCallback
function Ejemplo({ items }) {
// useMemo - memoriza el VALOR (número)
const total = useMemo(
() => items.reduce((sum, item) => sum + item.precio, 0),
[items]
); // total = 150
// useCallback - memoriza la FUNCIÓN
const calcularTotal = useCallback(
() => items.reduce((sum, item) => sum + item.precio, 0),
[items]
); // calcularTotal = () => {...}
// Son equivalentes:
// useCallback(fn, deps) === useMemo(() => fn, deps)
}

Cómo responder en entrevista:

useMemo retorna el resultado del cálculo; useCallback retorna la función. useCallback(fn, deps) es equivalente a useMemo(() => fn, deps). Usa useMemo para valores derivados costosos. Usa useCallback para funciones que pasas como props o dependencias de efectos.”


Definición profesional: Higher-Order Component que envuelve un componente y evita que se re-renderice si sus props no han cambiado (comparación superficial). Optimización de rendimiento.

React.memo
const ProductoItem = React.memo(function ProductoItem({ producto, onAgregar }) {
console.log('Renderizando:', producto.nombre);
return (
<div>
<h3>{producto.nombre}</h3>
<button onClick={() => onAgregar(producto.id)}>Agregar</button>
</div>
);
});
function ListaProductos({ productos }) {
// useCallback necesario: sin él, onAgregar es nueva función cada render
// y React.memo no sirve de nada
const onAgregar = useCallback(id => setCarrito(p => [...p, id]), []);
return productos.map(p => <ProductoItem key={p.id} producto={p} onAgregar={onAgregar} />);
}

Cómo responder en entrevista:

React.memo hace comparación superficial de props. Si las props no cambian, salta el render. Para que funcione con funciones/objetos como props, necesito useCallback/useMemo en el padre. Acepta un segundo argumento de comparación personalizada. No memoizar todo: tiene overhead. Solo donde hay renders costosos confirmados.”


Definición profesional: Hook alternativo a useState que centraliza la lógica de actualización de estado complejo en una función reducer pura. Inspirado en el patrón Redux.

useReducer
function formReducer(state, action) {
switch (action.type) {
case 'SET_FIELD':
return { ...state, [action.field]: action.value };
case 'SUBMIT_START':
return { ...state, isSubmitting: true };
case 'SUBMIT_SUCCESS':
return { ...initialState };
default:
return state;
}
}
function Formulario() {
const [state, dispatch] = useReducer(formReducer, {
nombre: '', email: '', isSubmitting: false
});
return (
<input
value={state.nombre}
onChange={e => dispatch({ type: 'SET_FIELD', field: 'nombre', value: e.target.value })}
/>
);
}

Cómo responder en entrevista:

useReducer centraliza la lógica de estado en un reducer puro: (state, action) => newState. Úsalo cuando el estado tiene múltiples valores relacionados, cuando las transiciones son complejas, o cuando el siguiente estado depende del anterior. El reducer es testeable de forma aislada. Es la base conceptual de Redux.”


15. ¿Cuándo usar useState vs useReducer?

Section titled “15. ¿Cuándo usar useState vs useReducer?”

Definición profesional: useState para valores simples e independientes. useReducer cuando varios estados están relacionados y sus transiciones son complejas.

useStateuseReducer
Estado simpleEstado complejo
Transiciones simplesMuchas transiciones
Valores independientesValores relacionados
useState vs useReducer
// Señal de migrar a useReducer:
const [nombre, setNombre] = useState('');
const [email, setEmail] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// 4+ useState relacionados → useReducer
const [state, dispatch] = useReducer(formReducer, {
nombre: '', email: '', loading: false, error: null
});

Cómo responder en entrevista:

“La regla práctica: cuando tengo 3 o más useState que se actualizan juntos o son interdependientes, migro a useReducer. También cuando la lógica de actualización es compleja y quiero testearla de forma aislada. useState sigue siendo preferible para valores simples como un booleano o un string.”


16. ¿Cuál es el ciclo de vida de un componente?

Section titled “16. ¿Cuál es el ciclo de vida de un componente?”

Definición profesional: Tres fases: Montaje (se crea e inserta en el DOM), Actualización (re-render por cambio de props o estado) y Desmontaje (se elimina). Con hooks funcionales, useEffect maneja las tres fases.

Ciclo de vida con useEffect
// Montaje: [] vacío = solo al montar
useEffect(() => {
console.log('Montado');
}, []);
// Actualización: con dependencias
useEffect(() => {
console.log('dependency cambió');
}, [dependency]);
// Desmontaje: función de cleanup
useEffect(() => {
const sub = subscribe();
return () => sub.unsubscribe(); // Cleanup = desmontaje
}, []);

Cómo responder en entrevista:

“En componentes funcionales, useEffect con [] vacío equivale a componentDidMount. Con dependencias es componentDidUpdate. La función de retorno es componentWillUnmount. A diferencia de las clases, un componente puede tener múltiples useEffect separados por preocupación.”


17. ¿Por qué useEffect se ejecuta dos veces en desarrollo?

Section titled “17. ¿Por qué useEffect se ejecuta dos veces en desarrollo?”

Definición profesional: React 18 con StrictMode monta, desmonta y vuelve a montar componentes intencionalmente en desarrollo para detectar efectos sin cleanup correcto. En producción solo se ejecuta una vez.

StrictMode y doble render
// StrictMode en index.jsx causa doble ejecución en desarrollo
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// Solución: siempre incluir cleanup
useEffect(() => {
const controller = new AbortController();
fetchData(controller.signal);
return () => controller.abort(); // Cleanup correcto
}, []);

Cómo responder en entrevista:

“React 18 StrictMode ejecuta efectos dos veces en desarrollo a propósito: monta, desmonta y remonta para verificar que el cleanup funciona correctamente. Si tu efecto se rompe con doble ejecución, le falta cleanup. En producción no ocurre.”


18. ¿Cómo hacer fetch de datos correctamente?

Section titled “18. ¿Cómo hacer fetch de datos correctamente?”

Definición profesional: El patrón correcto incluye: manejo de loading/error, cancelación con AbortController si el componente se desmonta, y evitar state updates en componentes desmontados.

useFetch custom hook
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) throw new Error('HTTP ' + response.status);
setData(await response.json());
} catch (err) {
if (err.name !== 'AbortError') setError(err);
} finally {
setLoading(false);
}
}
fetchData();
return () => controller.abort(); // Cancelar si desmonta
}, [url]);
return { data, loading, error };
}

Cómo responder en entrevista:

“El fetch en useEffect debe cancelarse si el componente se desmonta con AbortController. Manejo tres estados: loading, data, error. Para proyectos reales prefiero React Query o SWR: gestionan caché, revalidación, deduplicación de requests y estados de forma declarativa.”


Definición profesional: Versión síncrona de useEffect que se ejecuta después de mutaciones del DOM pero antes de que el navegador pinte. Útil para mediciones del DOM y cambios visuales que deben ser inmediatos.

useLayoutEffect
function Tooltip({ targetRef }) {
const tooltipRef = useRef(null);
// useEffect: puede causar parpadeo (paint → efecto → repaint)
// useLayoutEffect: sin parpadeo (DOM muta → efecto → paint)
useLayoutEffect(() => {
const target = targetRef.current.getBoundingClientRect();
tooltipRef.current.style.top = target.bottom + 'px';
}, []);
return <div ref={tooltipRef} className="tooltip">...</div>;
}

Cómo responder en entrevista:

useLayoutEffect se ejecuta síncronamente antes del paint del navegador. Úsalo solo cuando necesitas leer o mutar el DOM antes de que sea visible (posicionamiento, animaciones, scroll). La regla: si ves parpadeo con useEffect, cambia a useLayoutEffect. En SSR, useLayoutEffect da warnings; usa useEffect con un flag de hydration.”


20. ¿Cómo evitar memory leaks en useEffect?

Section titled “20. ¿Cómo evitar memory leaks en useEffect?”

Definición profesional: Un memory leak ocurre cuando un efecto actualiza el estado de un componente ya desmontado. La solución es siempre retornar una función de cleanup que cancele operaciones pendientes.

Cleanup en useEffect
useEffect(() => {
// 1. Fetch con AbortController
const controller = new AbortController();
fetch(url, { signal: controller.signal });
// 2. Timers
const timerId = setInterval(callback, 1000);
// 3. Event listeners
window.addEventListener('resize', handler);
// 4. Subscripciones
const sub = store.subscribe(handler);
return () => {
controller.abort();
clearInterval(timerId);
window.removeEventListener('resize', handler);
sub.unsubscribe();
};
}, []);

Cómo responder en entrevista:

“Siempre retorno una función de cleanup en useEffect. Las operaciones que hay que limpiar: fetch (AbortController), timers (clearTimeout/clearInterval), event listeners (removeEventListener) y subscripciones (unsubscribe). React 18 StrictMode ayuda a detectar efectos sin cleanup al montar/desmontar dos veces.”


Definición profesional: Funciones que comienzan con use y encapsulan lógica con estado (hooks) para reutilizarla entre componentes. Permiten extraer patrones comunes sin cambiar la estructura de componentes.

Custom hook: useLocalStorage
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// Uso - igual interfaz que useState
const [theme, setTheme] = useLocalStorage('theme', 'dark');
const [cart, setCart] = useLocalStorage('cart', []);

Cómo responder en entrevista:

“Los custom hooks son la forma moderna de compartir lógica con estado. Deben empezar con use para que el linter de hooks funcione. Encapsulan el estado y los efectos relacionados, dejando los componentes solo con renderizado. Reemplazan HOCs y Render Props para compartir lógica.”


22. ¿Cuáles son las reglas de los Hooks?

Section titled “22. ¿Cuáles son las reglas de los Hooks?”

Definición profesional: Dos reglas fundamentales: llamar hooks solo en el nivel superior (nunca dentro de condiciones o loops) y solo desde componentes de React o custom hooks. React rastrea hooks por orden de llamada.

Reglas de los Hooks
// ❌ MAL - orden condicional rompe el tracking de React
if (condition) {
useEffect(() => {});
}
// ✅ BIEN - condición dentro del hook
useEffect(() => {
if (condition) {
// lógica
}
}, [condition]);
// ❌ MAL - en función normal
function helper() {
useState(0); // Error
}
// ✅ BIEN - en componente o custom hook
function useHelper() {
useState(0); // OK - es custom hook
}

Cómo responder en entrevista:

“Las reglas existen porque React identifica cada hook por su posición en la lista de llamadas. Si los llamo condicionalmente, el orden puede cambiar entre renders y React asignará el estado al hook equivocado. El plugin eslint-plugin-react-hooks detecta violaciones automáticamente.”


Definición profesional: Hook (React 18) que genera IDs únicos y estables que coinciden entre servidor y cliente, resolviendo problemas de hidratación en SSR al asociar labels con inputs.

useId
function FormField({ label, type }) {
const id = useId();
return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} type={type} />
</div>
);
}
// Múltiples instancias generan IDs únicos
<FormField label="Email" type="email" /> // id=":r0:"
<FormField label="Password" type="password" /> // id=":r1:"

Cómo responder en entrevista:

useId genera IDs estables y únicos. El problema que resuelve: en SSR, si generaba IDs con Math.random() o contadores, diferían entre servidor y cliente causando errores de hidratación. useId garantiza el mismo ID en ambos lados. Nunca usarlo para keys de listas.”


Definición profesional: Hook de React 18 que marca actualizaciones de estado como no urgentes. React puede interrumpir y posponer estos renders para priorizar actualizaciones urgentes (input, clicks).

useTransition
function BuscadorGrande() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
setQuery(e.target.value); // Urgente: actualiza el input inmediatamente
startTransition(() => {
setResults(filterLargeList(e.target.value)); // No urgente: puede esperar
});
};
return (
<>
<input value={query} onChange={handleChange} />
{isPending ? <Spinner /> : <ResultList items={results} />}
</>
);
}

Cómo responder en entrevista:

useTransition es parte del Concurrent Mode de React 18. Marca actualizaciones como no urgentes para que React pueda interrumpirlas y renderizar primero las urgentes (input del usuario). isPending indica si hay una transición en progreso para mostrar feedback. Ideal para filtros, búsquedas y navegaciones costosas.”


Definición profesional: Hook de React 18 que retrasa la actualización de un valor hasta que el navegador tenga tiempo libre. Similar a useTransition pero para valores que vienen de props o de fuera del componente.

useDeferredValue
function SearchResults({ query }) {
// deferredQuery se actualiza cuando el navegador tiene tiempo
const deferredQuery = useDeferredValue(query);
const results = useMemo(
() => filterItems(deferredQuery), // Solo recalcula con el valor diferido
[deferredQuery]
);
// Mostrar estado "stale" mientras se actualiza
const isStale = query !== deferredQuery;
return (
<div style={{ opacity: isStale ? 0.7 : 1 }}>
<ResultList items={results} />
</div>
);
}

Cómo responder en entrevista:

useDeferredValue es el complemento de useTransition: úsalo cuando no controlas quién actualiza el valor (viene de props). useTransition envuelve el setState; useDeferredValue envuelve el valor resultante. Ambos permiten que la UI siga respondiendo durante renders costosos.”


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

Section titled “26. ¿Qué es el patrón Container/Presentational?”

Definición profesional: Separación entre componentes que gestionan lógica y datos (Container) y componentes que solo renderizan UI (Presentational). Mejora la reutilización y el testing.

Container/Presentational
// Container - lógica, estado, efectos
function UserListContainer() {
const [users, setUsers] = useState([]);
useEffect(() => { fetchUsers().then(setUsers); }, []);
return <UserList users={users} />;
}
// Presentational - solo UI, fácil de testear
function UserList({ users }) {
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}

Cómo responder en entrevista:

“El patrón Container/Presentational separa el ‘qué mostrar’ del ‘cómo obtenerlo’. Los componentes presentacionales son funciones puras: dado unas props, retornan JSX. Fáciles de testear y reutilizar. Hoy los custom hooks a menudo reemplazan los containers, pero el principio de separación sigue siendo válido.”


27. ¿Qué es el patrón Compound Components?

Section titled “27. ¿Qué es el patrón Compound Components?”

Definición profesional: Patrón donde un componente padre comparte estado implícito con sus hijos via Context, permitiendo APIs declarativas y flexibles (similar a select/option del HTML nativo).

Compound Components
const TabsContext = createContext();
function Tabs({ children, defaultTab }) {
const [activeTab, setActiveTab] = useState(defaultTab);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
{children}
</TabsContext.Provider>
);
}
Tabs.Tab = function Tab({ id, children }) {
const { activeTab, setActiveTab } = useContext(TabsContext);
return (
<button className={activeTab === id ? 'active' : ''}
onClick={() => setActiveTab(id)}>{children}</button>
);
};
// Uso declarativo y flexible
<Tabs defaultTab="home">
<Tabs.Tab id="home">Inicio</Tabs.Tab>
<Tabs.Tab id="about">Acerca</Tabs.Tab>
</Tabs>

Cómo responder en entrevista:

“Compound Components usan Context para compartir estado entre componentes relacionados sin props explícitos. Crean APIs declarativas donde el consumidor tiene control total sobre la estructura y orden. Headless UI y Radix UI usan este patrón extensamente.”


Definición profesional: Patrón donde un componente recibe una función como prop (render o children) y la llama con su estado interno, delegando el renderizado al consumidor.

Render Props
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMove = (e) => setPosition({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
}, []);
return render(position); // Delega el renderizado
}
// Uso
<MouseTracker render={({ x, y }) => <p>Posición: {x}, {y}</p>} />

Cómo responder en entrevista:

“Render Props compartían lógica con estado antes de los hooks. Hoy los custom hooks son más simples para el mismo propósito. Sin embargo, el patrón sigue siendo válido para casos donde el consumidor necesita control total sobre el renderizado, como en bibliotecas de componentes.”


29. ¿Qué son los Higher-Order Components (HOC)?

Section titled “29. ¿Qué son los Higher-Order Components (HOC)?”

Definición profesional: Función que recibe un componente y retorna un nuevo componente con funcionalidad adicional. Patrón de composición para reutilizar lógica transversal.

Higher-Order Component
function withAuth(WrappedComponent) {
return function AuthenticatedComponent(props) {
const { user } = useAuth();
if (!user) return <Navigate to="/login" />;
return <WrappedComponent {...props} user={user} />;
};
}
const ProtectedDashboard = withAuth(Dashboard);

Cómo responder en entrevista:

“Los HOCs son funciones de orden superior aplicadas a componentes. Útiles para lógica transversal: autenticación, logging, theming. La desventaja es el wrapper hell y dificultad para debuggear. Hoy los custom hooks son preferibles para compartir lógica. Los HOCs siguen siendo útiles en patrones de librería (connect de Redux, withRouter).“


Definición profesional: Los Custom Hooks comparten lógica sin modificar el árbol de componentes. Los HOCs modifican la estructura del árbol y pueden interceptar props. Hoy los Custom Hooks son la opción preferida.

HOCCustom Hooks
Modifica árbol de componentesSolo comparte lógica
Wrapper hell potencialSin overhead de árbol
Legacy patternModerno (preferido)

Cómo responder en entrevista:

“Custom Hooks son preferibles en la mayoría de casos: sin wrapper hell, fáciles de componer, mejor TypeScript. Los HOCs siguen siendo válidos cuando necesitas interceptar o modificar el ciclo de vida del componente (ej: React.memo, ErrorBoundary). Muchas librerías ofrecen ambas opciones.”


Definición profesional: API que permite a un componente recibir una ref como segundo argumento y pasarla a un elemento DOM interno. Necesario porque React no pasa ref como prop normal.

forwardRef
const Input = forwardRef(function Input({ label, ...props }, ref) {
return (
<div>
<label>{label}</label>
<input ref={ref} {...props} />
</div>
);
});
function Form() {
const inputRef = useRef(null);
return (
<>
<Input ref={inputRef} label="Email" type="email" />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</>
);
}

Cómo responder en entrevista:

forwardRef es necesario cuando un componente wrapper necesita exponer el DOM interno al padre. Sin él, ref no llega al input real. Se usa junto con useImperativeHandle para exponer solo una API controlada en lugar del nodo DOM completo.”


Definición profesional: Hook que personaliza el valor expuesto al padre cuando usa forwardRef. Permite exponer solo métodos específicos en lugar de todo el nodo DOM.

useImperativeHandle
const FancyInput = forwardRef(function FancyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
clear: () => { inputRef.current.value = ''; },
getValue: () => inputRef.current.value
// No expone el nodo DOM completo
}));
return <input ref={inputRef} {...props} />;
});
// Padre solo puede llamar focus, clear, getValue
const ref = useRef(null);
ref.current.focus();
ref.current.clear();

Cómo responder en entrevista:

useImperativeHandle limita la API imperativamente expuesta al padre. En lugar de dar acceso completo al nodo DOM (anti-patrón), expone solo métodos específicos. Es parte del principio de encapsulación. Útil en librerías de componentes donde quieres una API pública estable.”


Definición profesional: Componente que muestra un fallback mientras algún hijo está en estado “suspendido” (cargando código con lazy() o datos con librerías compatibles como React Query o Relay).

Suspense
const Dashboard = lazy(() => import('./Dashboard'));
function App() {
return (
<Suspense fallback={<Skeleton />}>
<Dashboard />
</Suspense>
);
}
// Anidado - fallback más granular
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
</Suspense>
</Suspense>

Cómo responder en entrevista:

Suspense permite loading states declarativos. Con lazy() maneja el code splitting. En React 18 también soporta data fetching con librerías como React Query (usando suspense: true). Los Suspense anidados permiten granularidad en los estados de carga.”


Definición profesional: Componentes de clase que implementan getDerivedStateFromError/componentDidCatch para capturar errores en su árbol de hijos y mostrar UI de fallback en lugar de romper toda la aplicación.

Error Boundary
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
logError(error, errorInfo); // Enviar a Sentry, etc.
}
render() {
if (this.state.hasError) {
return <ErrorFallback error={this.state.error} />;
}
return this.props.children;
}
}
// Uso - aislar secciones de la UI
<ErrorBoundary>
<RiskyComponent />
</ErrorBoundary>

Cómo responder en entrevista:

“Los Error Boundaries son la única forma de capturar errores de render en React. Solo pueden ser componentes de clase (hooks no pueden implementarlo aún). Úsalos en producción para aislar secciones: un error en el sidebar no debe romper el contenido principal. La librería react-error-boundary ofrece una versión lista para usar.”


Definición profesional: Función que permite importar componentes dinámicamente (code splitting), cargándolos solo cuando se necesitan. Debe usarse con Suspense.

React.lazy
// Code splitting por ruta
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
);
}
// Precargar en hover para mejor UX
const preload = () => import('./pages/Dashboard');
<Link to="/dashboard" onMouseEnter={preload}>Dashboard</Link>

Cómo responder en entrevista:

React.lazy habilita code splitting dinámico. El bundler (Webpack/Vite) separa el componente en un chunk separado que se descarga solo cuando se necesita. Combinado con Suspense y rutas, reduce el bundle inicial significativamente. Precargar en onMouseEnter mejora la UX percibida.”


Definición profesional: Librería estándar para routing declarativo en SPAs React. Versión 6 introdujo Routes/Route con elementos JSX y anidamiento más intuitivo.

React Router v6
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Inicio</Link>
<Link to="/about">Acerca</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<User />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}

Cómo responder en entrevista:

“React Router v6 usa Routes (en lugar de Switch) y Route con element (JSX en lugar de component). BrowserRouter usa la History API para URLs limpias sin hash. Link previene recargas de página. Las rutas se emparejan por especificidad, no por orden.”


37. ¿Cómo obtener parámetros de la URL?

Section titled “37. ¿Cómo obtener parámetros de la URL?”

Definición profesional: React Router v6 provee hooks para acceder a diferentes partes de la URL: useParams para segmentos dinámicos y useSearchParams para query strings.

Parámetros de URL
import { useParams, useSearchParams } from 'react-router-dom';
function User() {
const { id } = useParams(); // /users/:id → { id: "123" }
const [params, setParams] = useSearchParams(); // /users?page=2&sort=name
const page = params.get('page'); // "2"
const sort = params.get('sort'); // "name"
// Actualizar query params sin navegar
const changePage = (p) => setParams({ page: p, sort });
return <div>Usuario {id}, página {page}</div>;
}

Cómo responder en entrevista:

useParams da acceso a los segmentos dinámicos de la ruta (:id, :slug). useSearchParams gestiona los query params como un objeto URLSearchParams. setSearchParams actualiza los parámetros sin recargar la página. Para obtener el pathname completo, uso useLocation.”


38. ¿Cómo hacer navegación programática?

Section titled “38. ¿Cómo hacer navegación programática?”

Definición profesional: Navegar mediante código (no con clicks en Link) usando el hook useNavigate que retorna una función de navegación imperativa.

Navegación programática
import { useNavigate } from 'react-router-dom';
function Login() {
const navigate = useNavigate();
const handleLogin = async (credentials) => {
await loginAPI(credentials);
navigate('/dashboard'); // Navegar adelante
navigate('/login', { replace: true }); // Sin entrada en historial
navigate(-1); // Atrás
navigate('/profile', { state: { from: '/login' } }); // Con estado
};
}

Cómo responder en entrevista:

useNavigate retorna una función para navegar imperativamente, útil después de acciones asíncronas (login, submit). replace: true evita que el usuario vuelva atrás con el botón del navegador. navigate(-1) equivale a history.back(). El estado se puede pasar y leer con useLocation().state.”


Definición profesional: Componente wrapper que verifica autenticación antes de renderizar la ruta protegida, redirigiendo a login si no hay sesión activa.

Rutas protegidas
function ProtectedRoute({ children }) {
const { user, isLoading } = useAuth();
const location = useLocation();
if (isLoading) return <Spinner />; // Evitar parpadeo
if (!user) {
// Guardar la ruta original para redirigir después del login
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// Uso
<Route path="/dashboard" element={
<ProtectedRoute><Dashboard /></ProtectedRoute>
} />

Cómo responder en entrevista:

“El ProtectedRoute verifica autenticación y redirige si no hay usuario. Guardo la ruta original en state para redirigir al destino original después del login. El isLoading evita el parpadeo mientras se verifica la sesión. Para roles, extiendo el patrón con allowedRoles como prop.”


Definición profesional: Rutas definidas dentro de otras rutas que comparten layout. El componente Outlet en el layout renderiza la ruta hija activa.

Rutas anidadas
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} /> {/* /dashboard */}
<Route path="settings" element={<Settings />} /> {/* /dashboard/settings */}
<Route path="profile" element={<Profile />} /> {/* /dashboard/profile */}
</Route>
</Routes>
function DashboardLayout() {
return (
<div className="layout">
<Sidebar />
<main>
<Outlet /> {/* Aquí se renderiza la ruta hija */}
</main>
</div>
);
}

Cómo responder en entrevista:

“Las rutas anidadas permiten layouts compartidos. Outlet es el slot donde se renderiza la ruta hija activa. index define la ruta por defecto del layout. Es el patrón base para dashboards y apps con navegación lateral. En React Router v6 mejoró mucho respecto a v5.”


41. ¿Cómo testear componentes con React Testing Library?

Section titled “41. ¿Cómo testear componentes con React Testing Library?”

Definición profesional: Librería que testea componentes desde la perspectiva del usuario: busca elementos por roles accesibles, texto visible y labels, no por detalles de implementación.

React Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
test('muestra mensaje de error si el form está vacío', async () => {
render(<LoginForm />);
fireEvent.click(screen.getByRole('button', { name: /iniciar sesión/i }));
await waitFor(() => {
expect(screen.getByText(/email es requerido/i)).toBeInTheDocument();
});
});

Cómo responder en entrevista:

“RTL sigue la filosofía: testear el comportamiento, no la implementación. getByRole es el selector preferido (accesibilidad). findBy* para elementos asíncronos. userEvent sobre fireEvent para interacciones más realistas. No consulto por className o testId salvo como último recurso.”


Definición profesional: Con renderHook de Testing Library, que renderiza el hook en un componente mínimo. Los cambios de estado deben envolverse en act().

Testear custom hooks
import { renderHook, act } from '@testing-library/react';
test('useCounter incrementa y decrementa', () => {
const { result } = renderHook(() => useCounter(0));
expect(result.current.count).toBe(0);
act(() => { result.current.increment(); });
expect(result.current.count).toBe(1);
act(() => { result.current.decrement(); });
expect(result.current.count).toBe(0);
});

Cómo responder en entrevista:

renderHook renderiza el hook en un componente anónimo. act() es necesario para actualizaciones de estado síncronas. Para hooks con Context, paso un wrapper con el Provider. rerender permite cambiar las props del hook entre tests.”


Definición profesional: Reemplazar fetch global con un mock de Jest para controlar las respuestas sin hacer llamadas HTTP reales.

Mockear fetch
beforeEach(() => {
global.fetch = jest.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ users: [{ id: 1, name: 'Juan' }] })
});
});
afterEach(() => jest.restoreAllMocks());
test('carga y muestra usuarios', async () => {
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('Juan')).toBeInTheDocument();
});
expect(global.fetch).toHaveBeenCalledWith('/api/users');
});

Cómo responder en entrevista:

“Mockeo fetch con jest.fn() para tests unitarios. Para tests más completos uso msw (Mock Service Worker): intercepta requests a nivel de red, funciona en tests y en desarrollo. msw es la solución recomendada porque el código no necesita saber que está siendo mockeado.”


Definición profesional: Test que serializa el output del componente y lo guarda en un archivo. Futuros tests comparan contra ese snapshot para detectar cambios inesperados.

Snapshot testing
test('Button renderiza correctamente', () => {
const { asFragment } = render(
<Button variant="primary" onClick={jest.fn()}>
Guardar
</Button>
);
expect(asFragment()).toMatchSnapshot();
// Guarda el HTML serializado en __snapshots__/
});
// Actualizar snapshot si el cambio es intencional:
// jest --updateSnapshot

Cómo responder en entrevista:

“Los snapshots detectan cambios involuntarios en la UI. La desventaja: se actualizan fácilmente con --updateSnapshot sin revisar si el cambio es correcto, dando falsa seguridad. Prefiero tests de comportamiento específicos. Los snapshots son útiles para componentes estables y puramente visuales.”


Definición profesional: Pasar un wrapper a render que envuelve el componente con el Provider del Context con valores de prueba.

Testear Context
const mockUser = { name: 'Test', role: 'admin' };
const Wrapper = ({ children }) => (
<UserContext.Provider value={{ user: mockUser, logout: jest.fn() }}>
{children}
</UserContext.Provider>
);
test('muestra nombre del usuario', () => {
render(<UserMenu />, { wrapper: Wrapper });
expect(screen.getByText('Test')).toBeInTheDocument();
});
test('llama logout al hacer clic', () => {
const logout = jest.fn();
render(<UserMenu />, {
wrapper: ({ children }) => (
<UserContext.Provider value={{ user: mockUser, logout }}>
{children}
</UserContext.Provider>
)
});
fireEvent.click(screen.getByRole('button', { name: /cerrar sesión/i }));
expect(logout).toHaveBeenCalled();
});

Cómo responder en entrevista:

“Paso el Provider como wrapper en render. Para tests con múltiples providers (Router, Context, Theme), creo un AllProviders wrapper reutilizable. La clave es aislar el componente con los valores mínimos necesarios, no toda la configuración de producción.”


46. ¿Cómo identificar problemas de performance?

Section titled “46. ¿Cómo identificar problemas de performance?”

Definición profesional: React DevTools Profiler muestra qué componentes se re-renderizan, con qué frecuencia y cuánto tardan. Es la herramienta principal para diagnóstico de rendimiento.

Identificar problemas de performance
// 1. React DevTools Profiler - grabar y analizar renders
// 2. Por qué se re-renderizó (DevTools muestra la causa)
// 3. Detectar renders innecesarios con console
const Componente = React.memo(function Componente(props) {
console.count('Componente renderizado'); // Cuántas veces
return <div>{props.value}</div>;
});
// 4. React.StrictMode - detecta side effects problemáticos

Cómo responder en entrevista:

“Primero mido con React DevTools Profiler para identificar qué componentes se re-renderizan innecesariamente. No optimizo prematuramente. El Profiler muestra la causa del render (prop cambió, state cambió, parent renderizó). Con esa información decido si aplicar React.memo, useMemo o useCallback.”


Definición profesional: Las causas más comunes son: funciones u objetos creados inline (nueva referencia cada render), keys incorrectas en listas, y estado colocado demasiado arriba en el árbol.

Causas de re-renders
// ❌ Nueva función cada render - rompe React.memo
<Child onClick={() => handleClick(id)} />
// ✅ Referencia estable con useCallback
const handleClick = useCallback(() => {}, []);
<Child onClick={handleClick} />
// ❌ Nuevo objeto cada render
<Child style={{ color: 'red' }} />
// ✅ Objeto estable con useMemo o constante fuera del componente
const style = useMemo(() => ({ color: 'red' }), []);
// ❌ Índice como key
{items.map((item, i) => <Item key={i} />)}
// ✅ ID estable
{items.map(item => <Item key={item.id} />)}

Cómo responder en entrevista:

“Las principales causas: funciones/objetos inline crean nuevas referencias cada render (rompen React.memo), índice como key causa remontajes en reordenamiento, y estado demasiado arriba provoca renders en cascada. La solución es useCallback/useMemo para referencias estables y colocar el estado cerca de donde se usa.”


Definición profesional: Virtualización: solo renderizar los elementos visibles en la ventana. Las librerías react-window y @tanstack/virtual implementan este patrón.

Virtualización con react-window
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>{items[index].nombre}</div>
);
return (
<FixedSizeList
height={400} // Altura del contenedor visible
itemCount={items.length}
itemSize={35} // Altura de cada fila
width="100%"
>
{Row}
</FixedSizeList>
);
// Solo renderiza ~12 filas en lugar de 10,000
}

Cómo responder en entrevista:

“Para listas de miles de elementos, la virtualización renderiza solo los elementos visibles (~20-30). react-window es ligero; @tanstack/virtual es más flexible (soporta tamaños dinámicos). También aplico React.memo en el componente de cada fila para evitar re-renders al hacer scroll.”


Definición profesional: Conjunto de características de React 18 que permiten interrumpir renders largos para priorizar actualizaciones urgentes. Habilita useTransition, useDeferredValue, Suspense para datos y renderizado concurrente.

Concurrent Mode
// Activar Concurrent Mode (React 18)
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
// Features de Concurrent Mode:
// - useTransition: marcar renders como no urgentes
// - useDeferredValue: diferir actualizaciones de valor
// - Suspense para data fetching
// - Batching automático de actualizaciones

Cómo responder en entrevista:

“Concurrent Mode (React 18) permite que React interrumpa un render en progreso para procesar una actualización más urgente. createRoot lo activa (vs ReactDOM.render legacy). Incluye batching automático de setState, useTransition, useDeferredValue y mejor integración de Suspense.”


50. ¿Cómo hacer code splitting efectivo?

Section titled “50. ¿Cómo hacer code splitting efectivo?”

Definición profesional: Dividir el bundle JavaScript en chunks que se cargan bajo demanda. La estrategia más impactante es el splitting por ruta: cada página es un chunk separado.

Code splitting efectivo
// Por ruta - el más impactante
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
// Por feature - componentes pesados opcionales
const RichEditor = lazy(() => import('./components/RichEditor'));
// Precargar en hover para UX sin latencia percibida
const preload = () => import('./pages/Dashboard');
<Link to="/dashboard" onMouseEnter={preload}>Dashboard</Link>
// Vite/Webpack agrupan en chunks automáticamente
// Verificar con: npx vite-bundle-visualizer

Cómo responder en entrevista:

“El code splitting por ruta es lo más impactante: reduce el bundle inicial que el usuario descarga. React.lazy + Suspense lo hacen declarativo. El precarg en hover elimina la latencia percibida. Para analizar el bundle uso vite-bundle-visualizer o webpack-bundle-analyzer e identifico chunks grandes candidatos a splitting.”


🐝