Volver a Desarrollador

Desarrollo Frontend

Preguntas de entrevista de frontend — React, TypeScript y el navegador.

¿Cuál es la diferencia entre Component y PureComponent en React? Da un ejemplo donde PureComponent podría romper una aplicación.
Component se vuelve a renderizar cada vez que se llama a setState o cuando el padre se re-renderiza, sin importar si las props/state cambiaron. PureComponent implementa shouldComponentUpdate con una comparación superficial (shallow) de props y state, omitiendo el re-render si las referencias no cambiaron. Esto puede romper la app cuando se mutan objetos o arrays directamente (por ejemplo, hacer push a un array sin crear una nueva referencia): PureComponent no detecta el cambio y la UI queda desactualizada.
¿Por qué combinar React Context con shouldComponentUpdate puede ser peligroso?
shouldComponentUpdate (o PureComponent) puede bloquear el re-renderizado de componentes intermedios en el árbol. Si un valor de contexto cambia pero un ancestro retorna false en shouldComponentUpdate, sus hijos no se re-renderizarán aunque consuman ese contexto. Esto significa que los consumidores pueden mostrar datos obsoletos. La API moderna de Context (React 16.3+) mitiga esto propagando directamente a los consumidores, pero el contexto legacy era totalmente susceptible a este problema.
Describe tres formas de pasar información de un componente hijo a su componente padre en React.
1) Props de callback – el padre pasa una función como prop y el hijo la invoca con los datos. 2) Elevar el estado / contexto compartido – mover el estado al padre (o a un proveedor de contexto común) y que ambos componentes lo accedan. 3) Refs – el padre crea un ref (useRef / React.createRef) y lo pasa al hijo; el hijo puede exponer métodos imperativos (useImperativeHandle) o el padre lee valores del DOM directamente.
Da dos formas de evitar que un componente React se vuelva a renderizar innecesariamente.
1) React.memo (para componentes funcionales) o PureComponent (para clases) – envuelve el componente para que solo se re-renderice cuando sus props cambien según comparación superficial. 2) useMemo / useCallback – memoriza cálculos costosos o referencias de callbacks, de modo que los componentes que los reciben como props no vean nuevas referencias en cada render y omitan el re-renderizado.
¿Qué es un Fragment en React y por qué lo necesitamos? Da un ejemplo donde podría romper una aplicación.
Un Fragment (<React.Fragment> o <> </>) permite agrupar múltiples elementos sin añadir un nodo extra al DOM. Se necesita porque las expresiones JSX deben devolver un solo elemento raíz. Puede romper una app si dependes de relaciones directas padre-hijo en el DOM para CSS (por ejemplo, Flexbox o Grid) – al reemplazar un <div> contenedor con un Fragment se elimina el contenedor y los hijos pasan a ser hijos directos del abuelo, rompiendo el diseño esperado.
Da tres ejemplos del patrón Higher-Order Component (HOC) en React.
1) withRouter (React Router) – inyecta props de ruta (history, location, match) al componente envuelto. 2) connect (Redux) – mapea el estado del store y dispatch a las props del componente. 3) Un HOC personalizado withAuth que verifica autenticación y renderiza el componente envuelto o redirige a la página de login. Los HOCs reciben un componente y devuelven uno nuevo mejorado, promoviendo la reutilización de lógica transversal.
¿Cuántos argumentos recibe setState en React y por qué es asíncrono?
setState recibe hasta dos argumentos: 1) un actualizador – un objeto para hacer merge superficial en el state, o una función (prevState, props) => newState para actualizaciones que dependen del estado actual; 2) un callback opcional ejecutado después de que el estado se haya aplicado y el componente se haya re-renderizado. Es asíncrono (por lotes) por rendimiento: React agrupa múltiples llamadas a setState en un solo pase de re-renderizado para evitar renders intermedios innecesarios y reflujos de layout.
¿Qué pasos se necesitan para migrar un Class Component de React a un Function Component?
1) Reemplazar la declaración de clase por una función que recibe props como argumento. 2) Eliminar el constructor; convertir this.state en hooks useState. 3) Reemplazar los métodos de ciclo de vida (componentDidMount, componentDidUpdate, componentWillUnmount) con hooks useEffect. 4) Eliminar todas las referencias a 'this'. 5) Convertir los métodos de clase en funciones locales o usar useCallback. 6) Reemplazar this.props con el parámetro de props desestructurado. 7) Si se usa contexto, cambiar static contextType por useContext. 8) Si se usan refs, reemplazar createRef por useRef.
Enumera algunas formas en que se pueden usar estilos con componentes React.
1) Estilos inline mediante la prop style (un objeto JS con propiedades en camelCase). 2) Archivos CSS/SCSS importados directamente en el archivo del componente. 3) CSS Modules (nombres de clase con alcance local mediante *.module.css). 4) Bibliotecas CSS-in-JS (styled-components, Emotion) que generan estilos con alcance en tiempo de ejecución. 5) Frameworks utility-first como Tailwind CSS aplicados mediante strings en className.
¿Cómo se renderiza una cadena HTML proveniente del servidor en React?
Se usa la prop dangerouslySetInnerHTML: <div dangerouslySetInnerHTML={{ __html: htmlString }} />. El nombre es intencionalmente alarmante porque inyectar HTML crudo es un riesgo de XSS. Siempre se debe sanitizar la cadena del lado del servidor o con una biblioteca confiable (por ejemplo, DOMPurify) antes de renderizar. Alternativamente, se puede parsear el HTML en un árbol de elementos React usando una biblioteca como html-react-parser si se necesita control a nivel de componente.
¿Cómo implementarías el resaltado de texto de caracteres coincidentes en un componente autocompletado en React?
Divide el texto de la sugerencia en partes basándote en la consulta usando una regex insensible a mayúsculas o coincidencia por índice de cadena. Renderiza las partes que no coinciden como texto plano y envuelve las subcadenas coincidentes en un <mark> o <span> con una clase CSS de resaltado. Por ejemplo: text.split(new RegExp((${query escapado}), 'gi')).map(part => coincide ? <strong>{part}</strong> : part). Asegúrate de escapar los caracteres especiales de regex en la consulta.
¿Qué casos límite deberías manejar en un componente autocompletado React para una experiencia de usuario perfecta?
Los casos límite clave incluyen: debouncing del input para evitar llamadas API excesivas, manejar resultados vacíos de forma elegante, navegación por teclado (flechas, Enter, Escape), cerrar el dropdown al perder foco o clic fuera, manejar escritura rápida que cause respuestas asíncronas desordenadas (condiciones de carrera), recortar espacios en blanco, mostrar indicador de carga, prevenir el envío de formulario con Enter al seleccionar, y accesibilidad (atributos ARIA, anuncios para lectores de pantalla).
¿Por qué la función de obtención de datos en un componente autocompletado debería ser asíncrona incluso cuando se usan datos mock locales?
Hacer la función de filtro asíncrona simula condiciones del mundo real donde los datos provienen de una solicitud de red con latencia. Esto te obliga a manejar estados de carga, condiciones de carrera (respuestas obsoletas que llegan después de consultas más recientes) y desmontaje del componente durante solicitudes pendientes. También deja el componente listo para reemplazar datos mock por una API real sin cambios arquitectónicos.
¿Cómo puedes manejar condiciones de carrera en un autocompletado React que obtiene sugerencias asincrónicamente?
Usa un mecanismo de limpieza para que solo el resultado de la última solicitud se aplique. En un useEffect, retorna una función de limpieza que establece un flag de cancelación; cuando la respuesta asíncrona llega, verifica el flag antes de actualizar el estado. Alternativamente, usa un AbortController para cancelar llamadas fetch previas, o mantén un contador de ID de solicitud y aplica solo los resultados cuyo ID coincida con la última solicitud despachada.
¿Por qué React requiere usar solo componentes funcionales con hooks para un componente moderno?
Los componentes funcionales con hooks proveen un modelo mental más simple: sin problemas de binding de 'this', mejor separación de responsabilidades mediante hooks personalizados, pruebas más fáciles y mejor alineación con las funcionalidades concurrentes de React. Hooks como useState, useEffect, useRef y useMemo cubren todos los patrones de ciclo de vida de clases de forma composable. El equipo de React recomienda hooks para todo código nuevo porque reducen el boilerplate y fomentan la reutilización de lógica sin HOCs o render props.
¿Cómo manejas los fallos de API de forma elegante en un frontend React?
Envuelve la llamada fetch/axios en un bloque try-catch y mantén una variable de estado de error. Si la solicitud falla o devuelve un estado no 2xx, establece el estado de error y renderiza un mensaje de error amigable en lugar de los resultados. También deshabilita el botón de envío o muestra un spinner mientras la solicitud está en curso.
¿Cuáles son las consideraciones clave para hacer un formulario de entrada de datos responsivo y amigable en React?
Usa componentes controlados para que el estado del formulario siempre refleje la UI, proporciona etiquetas claras y texto de marcador de posición, da retroalimentación de validación inmediata y maneja los estados de carga y error con indicadores visibles. Para una entrada de matriz, un textarea con fuente monoespaciada facilita la lectura de la cuadrícula.
¿Cómo se implementan rutas protegidas por rol en una SPA de React sin un backend real?
Se mantiene un estado de autenticación simulado (p. ej. en React Context o una store global, persistido en sessionStorage/localStorage) que almacena el rol seleccionado. Un componente ProtectedRoute verifica el rol actual; si no cumple, redirige al login usando <Navigate>. Cada árbol de rutas (/taller/..., /cliente/...) se envuelve en su propio guard, por lo que el acceso directo por URL siempre queda bloqueado.
¿Qué es el patrón Container/Presenter en React y por qué es valioso?
Los componentes contenedor (smart) poseen el estado y la lógica de negocio y pasan datos hacia abajo mediante props. Los componentes presentador (dumb) son unidades de renderizado puro sin efectos secundarios. La separación mejora la capacidad de prueba (los presentadores son fácilmente testeables), la reutilización y la legibilidad, ya que las preocupaciones de UI quedan completamente separadas del dominio.
¿Cómo implementarías una máquina de estados finita para el ciclo de vida de una orden en React?
Define un mapa de transiciones permitidas (p. ej. { CREATED: ['DIAGNOSED','CANCELLED'], DIAGNOSED: ['AUTHORIZED','CANCELLED'], ... }). Expone una función transition(order, newStatus) en un módulo de dominio puro que valida el movimiento contra el mapa, agrega un registro de Evento y devuelve un nuevo objeto de orden. El estado de React se actualiza reemplazando la orden en el arreglo; la UI deriva qué botones de acción mostrar a partir del estado actual.
¿Cómo se mantienen sincronizados el estado de React y localStorage de forma confiable?
Se usa un hook personalizado (p. ej. useLocalStorage) que inicializa el estado leyendo desde localStorage y envuelve el setter para que cada actualización también llame a localStorage.setItem. Para grafos de objetos complejos se serializa con JSON.stringify/JSON.parse. La data por defecto se siembra una vez en la primera carga comprobando si la clave está ausente. Este patrón garantiza que un refresco de página siempre hidrate el estado correcto.
¿Cómo se aplica el Principio Abierto/Cerrado de SOLID al agregar nuevos estados o reglas de negocio en un frontend React?
Se mantiene cada regla de negocio en su propia función o clase en una capa de dominio (p. ej. src/domain/orders/). Agregar un nuevo estado o regla implica añadir una entrada al mapa de transiciones y una nueva función validadora, sin tocar las existentes. Los componentes de UI solo consumen la API del dominio, por lo que tampoco requieren cambios. Esto evita modificar código probado y limita el impacto de nuevos requerimientos.
¿Qué significa 'arquitectura hexagonal aplicada al frontend' en la práctica?
La capa de dominio (lógica de negocio pura, sin imports de React) está en el centro. Los adaptadores la rodean: los adaptadores de UI son componentes y hooks de React que llaman a funciones del dominio; los adaptadores de persistencia son módulos que leen/escriben en localStorage. El dominio nunca importa de React ni localStorage, solo trabaja con datos planos. Esto hace que el dominio sea testeable de forma independiente y fácilmente reemplazable.
¿Cómo se manejan y muestran las violaciones de reglas de negocio (p. ej. NO_SERVICES, REQUIRES_REAUTH) en una UI de React sin romper la aplicación?
Las violaciones de reglas de negocio deben devolverse como objetos de error tipados desde las funciones del dominio, en lugar de lanzar excepciones. El hook o reducer que los llama los agrega a un arreglo errors en la orden y actualiza el estado de la UI. Los componentes renderizan una lista de errores o un toast basado en ese arreglo. Como la ejecución continúa normalmente, la app nunca se rompe; los errores son parte del modelo de dominio y son visibles en el historial de la orden.
¿Cómo se implementa un historial de eventos/auditoría inmutable para entidades del dominio en una aplicación frontend?
Cada mutación produce un nuevo registro de Evento (con id, type, fromStatus, toStatus, timestamp) y lo agrega al arreglo events de la orden. El arreglo nunca se modifica en su lugar; en cambio, se devuelve y almacena un nuevo objeto de orden con la lista de eventos extendida. Esto crea un registro completo y cronológico de lo ocurrido en la orden, visible tanto para el taller como para el cliente, y es fácilmente testeable porque el historial es datos planos.
¿Qué es el diseño mobile-first y cómo influye en las decisiones de CSS y componentes en un proyecto React?
Mobile-first significa escribir estilos base para la viewport más pequeña y agregar sobrescrituras para pantallas más grandes usando media queries con min-width. En React esto influye en las opciones de layout (flexbox apilado en lugar de grids para móvil, progressive disclosure para datos densos), manejo de tablas (colapsar a listas de tarjetas en pantallas pequeñas) y patrones de navegación (tab bars inferiores o menús hamburguesa en lugar de sidebars). Comenzar pequeño obliga a priorizar los elementos más críticos de la UI.
¿Cómo implementarías la guardia de sobrecosto del 110 % como función reutilizable y testeable en la capa de dominio?
Se escribe una función pura checkCostOverrun(order): BusinessError | null que calcula limite = order.authorizedAmount * 1.10 y lo compara con order.realTotal. Si realTotal > limite, devuelve un objeto de error REQUIRES_REAUTH; de lo contrario devuelve null. Esta función se invoca tras cualquier actualización de costo y en la transición IN_PROGRESS → COMPLETED. Al ser pura, puede testearse unitariamente con un simple fixture de valores sin montar ningún componente.
¿Cómo se estructura un proyecto React usando organización modular por dominio?
Se agrupan los archivos por slice de dominio en lugar de por tipo técnico. Por ejemplo: src/domain/orders/ (tipos, máquina de estados, reglas de negocio), src/domain/clients/ (tipos, consultas), src/domain/auth/ (contexto de rol, guardias), src/shared/ (primitivos de UI, hooks, utils). Cada módulo de dominio exporta una API pública y oculta detalles internos. Esto mantiene el código relacionado co-ubicado, hace seguras las eliminaciones y escala bien al agregar nuevos dominios.
¿Cómo se prueban unitariamente las reglas de negocio (transiciones de estado, cálculos de montos) en un proyecto React sin backend?
Se extrae toda la lógica de negocio en funciones o clases puras en la capa de dominio, sin dependencias de React. Los tests importan estas funciones directamente y verifican los valores devueltos o los códigos de error. Por ejemplo: expect(transition(order, 'AUTHORIZED')).toEqual({ ...order, status: 'AUTHORIZED' }). Vitest o Jest son elecciones habituales. Esta separación permite que los tests corran instantáneamente sin jsdom, React Testing Library ni montaje de componentes.
¿Cómo se calcula y formatea el monto autorizado con IVA en un frontend JavaScript?
Se calcula authorizedAmount = Math.round(subtotalEstimado * 1.16 * 100) / 100 para evitar la deriva de punto flotante y producir un valor con 2 decimales. Para la visualización se usa Intl.NumberFormat con el locale y la opción de moneda adecuados, en lugar de concatenación manual de cadenas. El valor numérico crudo se guarda en el estado y solo se formatea en el momento del renderizado para que las comparaciones y cálculos siempre operen sobre números.
¿Qué estrategias se pueden usar para gestionar el estado global en una SPA de React de tamaño mediano sin introducir Redux?
React Context combinado con useReducer es el enfoque ligero más común: un contexto almacena el estado (órdenes, clientes, rol de autenticación) y una función dispatch expone acciones tipadas. Para mejor rendimiento se separan los contextos por preocupación (AuthContext, OrderContext) para evitar re-renderizados de componentes no relacionados. Los hooks personalizados (useOrders, useAuth) abstraen el consumo del contexto. Si la complejidad crece, Zustand es una alternativa mínima que evita el boilerplate.
¿Cómo se diseña un modelo de dominio para una SPA frontend de modo que cada entidad referencie a otras por ID en lugar de incrustar objetos completos?
Se almacena cada tipo de entidad en su propia colección normalizada (p. ej., customers: Record<string,Customer>, orders: Record<string,RepairOrder>). Las entidades se referencian entre sí mediante IDs (customerId, vehicleId). Los componentes de UI reciben el grafo de objetos completo solo cuando se necesita, ensamblado en un selector o hook. Esto refleja lo que devolvería una API backend, evita datos duplicados y hace que las actualizaciones de entidades individuales sean O(1) sin copias en cascada.
¿Cómo se garantiza que las reglas de negocio no puedan ser eludidas manipulando la UI directamente (p. ej. haciendo clic en un botón oculto)?
Las reglas de negocio deben vivir en la capa de dominio, no solo en condicionales de UI que ocultan botones. Cada acción que muta el estado pasa por una función de dominio que revalida las precondiciones antes de producir un nuevo estado. Deshabilitar u ocultar un botón en la UI es una conveniencia; la guardia autoritativa siempre está en la función del dominio. Para una app solo frontend, localStorage puede manipularse, por lo que al cargar se deben validar y sanitizar los datos antes de usarlos.
¿Qué patrones de hooks de React son más útiles para encapsular la lógica del ciclo de vida de una orden?
useReducer es ideal para estados complejos con muchas transiciones: cada tipo de acción se mapea a una llamada de función del dominio. El reducer y el dispatch se envuelven en un hook personalizado useOrders que también sincroniza con localStorage en cada dispatch. useMemo puede derivar valores calculados (p. ej., lista filtrada de órdenes, límite del 110 %) del estado crudo sin re-ejecutarse en cada renderizado. useCallback estabiliza los manejadores de eventos pasados a componentes hijos para evitar re-renders innecesarios.
¿Qué es el Cross-Site Scripting (XSS) y cómo se previene?
XSS es una vulnerabilidad que permite a un atacante inyectar scripts maliciosos en una página vista por otros usuarios, posibilitando el robo de cookies, secuestro de sesión o manipulación del DOM. La regla fundamental es nunca confiar en la entrada del usuario. Medidas clave: escapar/codificar toda salida antes de renderizarla, aplicar una cabecera Content-Security-Policy, evitar innerHTML en favor de textContent y usar frameworks que escapen automáticamente (React lo hace por defecto).
¿Qué es el Clickjacking y cómo se defiende contra él?
El Clickjacking (ataque de redirección de UI) engaña al usuario para que haga clic en un elemento oculto bajo una superposición transparente. El atacante incrusta tu página en un iframe y coloca su propia UI encima. Las defensas principales son la cabecera HTTP X-Frame-Options (con valor DENY o SAMEORIGIN) o la directiva frame-ancestors de Content-Security-Policy, que impiden que tu página sea incrustada en iframes de otros orígenes.
¿Es seguro almacenar tokens de autenticación en localStorage o sessionStorage? ¿Dónde deben almacenarse?
No. Ambos almacenamientos son accesibles por cualquier JavaScript en la página, lo que hace los tokens vulnerables a XSS. La solución recomendada son las cookies HttpOnly: el navegador las envía automáticamente con solicitudes del mismo origen pero JavaScript no puede leerlas, eliminando el vector XSS. Como contrapartida, también hay que implementar protección CSRF (SameSite=Strict o un token CSRF).
¿Qué es React Fiber y por qué fue introducido?
React Fiber es una reescritura completa del algoritmo de reconciliación de React, lanzada en React 16. Antes de Fiber, la reconciliación era síncrona y recursiva y no podía interrumpirse, causando UIs lentas en árboles grandes. Fiber representa cada unidad de trabajo como un nodo en una lista enlazada, permitiendo a React dividir el renderizado en fragmentos, pausar, priorizar y reanudar el trabajo. Esto habilitó características de Concurrent Mode como Suspense, error boundaries y hooks.
¿Cómo funciona la Reconciliación de React?
Cuando el estado cambia, React construye un nuevo árbol de DOM virtual y lo compara con el anterior usando un algoritmo heurístico O(n). Primero compara los tipos de elementos—si el tipo cambia, React destruye y reconstruye el subárbol completo. Si el tipo es el mismo, actualiza solo los atributos modificados. Para las listas usa la prop key para emparejar elementos antiguos y nuevos, permitiendo reutilizar los elementos estables sin re-montarlos.
¿Por qué es importante la prop key en las listas de React y por qué es mala práctica usar el índice del array como key?
La prop key permite al reconciliador de React identificar qué elementos han cambiado, sido añadidos o eliminados sin re-montar los que no han cambiado. Usar el índice del array como key es problemático cuando la lista se reordena, filtra o se insertan/eliminan elementos: React reutiliza instancias de componentes incorrectas, causando estado incorrecto y re-renders innecesarios. Se debe usar un identificador estable y único (p. ej., un ID de base de datos).
¿Qué es un Higher-Order Component (HOC) en React?
Un HOC es una función que recibe un componente y devuelve un nuevo componente con props o comportamiento adicionales inyectados—una aplicación del patrón decorador. Los HOCs permiten reutilizar aspectos transversales como guardas de autenticación o seguimiento analítico sin modificar el componente envuelto. Han sido sustituidos en gran medida por los custom hooks, que son más sencillos y evitan problemas de colisión de props y árboles de componentes difíciles de depurar.
¿Qué son los React Portals y cuándo deben usarse?
Los Portals renderizan un componente hijo en un nodo del DOM que existe fuera de la jerarquía DOM del componente padre, manteniendo al hijo dentro del árbol de componentes React—el contexto y la propagación de eventos siguen funcionando. Úsalos para elementos de UI que deben escapar visualmente de overflow:hidden o contextos de apilamiento z-index: diálogos modales, tooltips, popovers y notificaciones que necesitan renderizarse al nivel del document body.
¿Qué problema resuelve useTransition (o startTransition) en el Concurrent Mode de React?
Abordan el problema del renderizado bloqueante marcando las actualizaciones de estado como no urgentes. React puede interrumpir dicha actualización si llega trabajo más urgente—como una pulsación de tecla—manteniendo la UI responsiva durante re-renders costosos. useTransition además expone un booleano isPending para mostrar un indicador de carga mientras la transición está en curso.
Si un manejador de eventos llama a setState tres veces, ¿cuántos renders disparará React?
En React 18, solo uno, porque el batching automático agrupa todas las actualizaciones de estado dentro de cualquier contexto asíncrono (setTimeout, Promises, manejadores de eventos nativos) en un único re-render. Antes de React 18, el batching se limitaba a los manejadores de eventos sintéticos de React, por lo que tres llamadas a setState fuera de ese contexto podían disparar hasta tres renders separados.