Buenas prácticas de fundamentos de React
Los 20 fundamentos básicos principales
Los bloques de construcción esenciales que todo aprendiz básico de React debería conocer antes de adentrarse en patrones avanzados.
- Los componentes son funciones que devuelven JSX: Un componente de React es simplemente una función de JavaScript/TypeScript que devuelve marcado -- el nombre de la función debe empezar con mayúscula.
- JSX no es HTML: JSX se parece a HTML pero compila a JavaScript -- usa
classNameen lugar declass,htmlForen lugar defor, y camelCase para atributos de eventos comoonClick. - Las llaves incrustan expresiones: Usa
{variable}dentro de JSX para mostrar valores dinámicos -- puedes poner cualquier expresión de JavaScript dentro, pero no sentencias comoifofor. - Las props pasan datos hacia abajo: Los componentes padres envían datos a los hijos mediante props -- piensa en ellas como argumentos de función que fluyen en una sola dirección, del padre al hijo.
- Las props son de solo lectura: Nunca modifiques una prop directamente -- si un hijo necesita cambiar algo, el padre debería pasarle una función setter en su lugar.
- useState añade interactividad: Llama a
useState(initialValue)para obtener un par[value, setValue]-- actualizar el estado con el setter dispara un nuevo render. - Los eventos usan manejadores en camelCase: Adjunta manejadores de eventos como
onClick={handleClick}-- pasa la referencia de la función, nunca la llames con paréntesis comoonClick={handleClick()}. - Las listas necesitan una prop key: Al renderizar arrays con
.map(), cada elemento necesita unakeyúnica para que React pueda rastrear de forma eficiente las adiciones, eliminaciones y reordenamientos. - El renderizado condicional usa ternarios o &&: Muestra u oculta elementos con
{condition ? <A /> : <B />}o{condition && <A />}, ya que no puedes usar sentenciasifdirectamente dentro de JSX. - Los inputs controlados combinan value con onChange: Establece
value={state}yonChange={e => setState(e.target.value)}juntos -- establecervaluepor sí solo hace que el input sea de solo lectura. - children es una prop especial: Todo lo que coloques entre las etiquetas de apertura y cierre de un componente se convierte en
children-- así es como construyes componentes envoltorio y de layout. - useEffect ejecuta efectos secundarios: Usa
useEffect(() => { ... }, [deps])para cosas que ocurren fuera del render, como llamadas a API, temporizadores o suscripciones. - El array de dependencias controla cuándo se vuelven a ejecutar los efectos: Un array vacío
[]significa "ejecutar una vez al montar" -- listar variables significa "volver a ejecutar cuando estas cambien" -- omitirlo significa "ejecutar después de cada render". - import y export conectan archivos: Usa
export defaultoexportpara hacer que un componente esté disponible, luegoimporten otro archivo para usarlo -- el alias@/apunta a la raíz de tu proyecto. - Los Fragments evitan nodos DOM adicionales: Envuelve elementos hermanos en
<>...</>(Fragment) cuando no necesitas un<div>adicional -- esto mantiene limpio tu HTML renderizado. - Los estilos en línea usan objetos de JavaScript: Escribe
style={{ color: "red", fontSize: "16px" }}con propiedades en camelCase -- pero en la práctica, usa un framework de CSS como Tailwind en su lugar. - Eleva el estado al padre común más cercano: Cuando dos componentes hermanos necesitan los mismos datos, mueve el estado hacia su padre y pásalo hacia abajo como props.
- Deriva valores en lugar de sincronizar estado: Si un valor puede calcularse a partir de estado o props existentes, calcúlalo durante el render en lugar de almacenarlo en un
useStateseparado. - TypeScript detecta errores antes de que lleguen a los usuarios: Tipa tus props con
interfaceotypey usa archivos.tsx-- el compilador señalará props faltantes, tipos incorrectos y erratas al instante. - Mantén los componentes pequeños y enfocados: Cada componente debería hacer una sola cosa bien -- si un componente se está volviendo largo, extrae partes de él en componentes hijos más pequeños.
Las 25 mejores prácticas intermedias principales
Un resumen condensado de las 25 mejores prácticas más importantes extraídas de cada página de esta sección.
- Nunca definas componentes dentro de componentes: Un componente declarado dentro de otro componente obtiene una identidad nueva en cada render del padre, por lo que React lo desmonta y lo vuelve a montar cada vez — destruyendo el estado local y los efectos; siempre eleva los componentes al ámbito del módulo:
function Parent() { function Child() {} /* mal — Child se vuelve a montar en cada render */ }. - Trata las props como inmutables: Mutar una prop (por ejemplo,
props.items.push(x)) muta silenciosamente los datos del padre y rompe el modelo de renderizado de React; copia en nuevos arrays u objetos antes de modificar y deja que el padre sea dueño del estado. - Extiende las props nativas con ComponentPropsWithoutRef: Tipa los componentes envoltorio con
React.ComponentPropsWithoutRef<"button">para que cada atributo nativo se mantenga sincronizado con la API del DOM; volver a declarar las props manualmente se desincroniza en cuanto cambia la especificación del DOM. - Prefiere interface para las Props:
interfaceofrece mejores mensajes de error y admite la fusión de declaraciones, así que úsala para las formas de las props de los componentes; reservatypepara uniones, tipos mapeados y otras transformaciones que las interfaces no pueden expresar. - Protégete del 0 en los condicionales &&:
{count && <Badge />}renderiza la cadena literal "0" cuandocountes 0 porque 0 es falsy pero aún así renderizable; usacount > 0 && <Badge />o un ternario para evitar el infame bug del "0". - Uniones discriminadas para el estado: Modela el estado como
{ status: "success"; data } | { status: "error"; error }para que TypeScript acote dentro de cada rama; esto elimina el ruido del encadenamiento opcional y hace obvias las ramas inalcanzables. - Cambiar el tipo de elemento reinicia el estado: Cambiar el tipo de un componente en la misma posición del árbol (por ejemplo,
<Spinner />→<DataTable />, o<input>↔<textarea>) desmonta el subárbol antiguo y borra su estado; mantén el mismo tipo y alterna las props cuando quieras que el estado persista. - Pasa manejadores, no invocaciones:
onClick={handleClick}adjunta la función;onClick={handleClick()}la llama durante el render y conecta el valor de retorno como el manejador, lo cual casi nunca es lo que quieres — envuelve en una arrow function cuando necesites inyectar argumentos:onClick={() => handleClick(id)}. - onChange se dispara en cada pulsación de tecla: El
onChangede React se corresponde con el evento nativoinput, no con elchangenativo, por lo que se dispara en cada pulsación de tecla en lugar de al perder el foco — usaonBlursi de verdad quieres la semántica de "confirmar al perder el foco". - Usa currentTarget, no target:
e.targetes el elemento en el que se originó el evento (que puede ser un hijo), mientras quee.currentTargetes el elemento al que está adjunto el manejador; lee los atributos que tú controlas desdecurrentTargetpara evitar sorpresas al hacer clic en elementos anidados. - Recurre a addEventListener cuando sea necesario: Las props de manejador de JSX no admiten
{ passive: false }ni eventos a nivel dewindow/document; para escuchadores de la tecla Escape, monitores de scroll otouchmoveque necesitenpreventDefault, usauseEffect+addEventListenersobre una ref y devuelve una función de limpieza. - Los inputs controlados necesitan value + onChange: Establecer
valuesin unonChangecorrespondiente hace que el input sea de solo lectura porque React fija el valor del DOM al estado; o bien combina ambos —<input value={name} onChange={e => setName(e.target.value)} />— o cambia adefaultValuepara un input no controlado. - Los checkboxes y radios usan checked: Los inputs de tipo checkbox y radio consumen
checked/defaultChecked, novalue/defaultValue; confundirlos hace lo incorrecto en silencio, ya que elvaluede un checkbox es el token enviado, no el estado de marcado:<input type="checkbox" checked={on} onChange={e => setOn(e.target.checked)} />. - Llama a useFormStatus en un hijo:
useFormStatus()solo devuelve información de estado pendiente precisa cuando se llama desde dentro de un descendiente del<form>; llamarlo en el mismo componente que renderiza el formulario devuelve valores predeterminados obsoletos sin ningún error, así que extrae el botón de envío en su propio componente. - Parsea las cadenas de input.value:
e.target.valuesiempre es una cadena — incluso para<input type="number">— así que envuélvela conNumber(...)oparseInt(...)antes de usarla en cálculos o almacenar estado tipado:onChange={e => setAge(Number(e.target.value))}. - Usa className, no class: JSX compila a
React.createElement, por lo que el atributoclassde HTML esclassName,foreshtmlFor, y los estilos toman un objeto en camelCase; usar los nombres de HTML compila pero genera advertencias y puede descartar estilos silenciosamente en entornos estrictos. - Los objetos no son children válidos: Renderizar
{user}lanza "Objects are not valid as a React child"; renderiza un campo específico como{user.name}oJSON.stringify(user)para salida de depuración, y recuerda que0se renderiza como texto peronull/false/undefinedno renderizan nada. - ReactNode para children, JSX.Element para retornos: Tipa
childrencomoReact.ReactNode(que cubre JSX, cadenas, números, arrays,null) y reservaReact.JSX.Elementpara los tipos de retorno que siempre devuelven un único elemento JSX. - Keys únicas entre hermanos: La
keyde una lista debe ser única entre sus hermanos (no globalmente), y debe ser estable entre renders — usaitem.id, noMath.random()niDate.now(), o React volverá a montar cada fila y destruirá su estado. - Evita las keys de índice en listas dinámicas: Usar
indexcomokeyen listas que se reordenan, filtran o insertan hace que React reutilice los nodos DOM incorrectos, produciendo valores de input obsoletos y foco roto; las keys de índice solo son seguras para listas verdaderamente estáticas. - Usa key para forzar un nuevo montaje: Cambiar la
keyde un componente es la forma canónica de reiniciar todo su estado interno —<PlayerProfile key={currentPlayerId} />vuelve a montarse deliberadamente cuando cambia el jugador, lo cual es una característica, no un truco. - Los Fragments cortos no pueden llevar key: La forma abreviada
<>...</>no acepta una propkey; cuando mapees fragments, importaFragmenty usa<Fragment key={...}>...</Fragment>para evitar advertencias de key duplicada o renders sin key. - ref.current no dispara renders: Actualizar
ref.currentno vuelve a renderizar el componente — ese es el punto; recurre auseRefcuando la UI no dependa del valor (IDs de temporizador, observers, caché del último valor) y auseStatecuando sí dependa. - ref es una prop normal en React 19:
forwardRefestá obsoleto; simplemente tiparefcomo una prop ordinaria conReact.Ref<HTMLInputElement>y desestructúrala junto a las demás — y recuerda que{...props}ahora también esparceref, así que extráela explícitamente cuando la pases a través. - Devuelve una limpieza desde los callbacks de ref: Los callbacks de ref de React 19 pueden devolver una función de limpieza como
useEffect, así que conecta los observers comoref={node => { if (!node) return; const obs = new ResizeObserver(...); obs.observe(node); return () => obs.disconnect(); }}en lugar del antiguo baile de comprobación de null.