Flujo de datos en React: fuente de verdad, derivaciones y elevación de estado

Capítulo 9

Tiempo estimado de lectura: 8 minutos

+ Ejercicio

Fuente de verdad: un solo lugar para cada dato

En React, un dato que cambia debería tener una fuente de verdad: un único estado que lo representa. Cuando el mismo concepto aparece en varios estados (duplicado), empiezan los problemas: inconsistencias, bugs “fantasma” y lógica extra para mantener todo sincronizado.

Señales de que duplicaste estado

  • Actualizas un estado y “olvidas” actualizar otro.
  • Dos componentes muestran el “mismo” dato pero a veces difieren.
  • Necesitas useEffect solo para copiar valores entre estados.

Diagrama conceptual: fuente de verdad y derivaciones

[Estado (fuente de verdad)] --(render)--> [UI] --(eventos)--> [setState] --> [Estado]

Idea clave: si un valor puede calcularse a partir de otro, normalmente no merece un estado propio; es una derivación.

Derivaciones: calcula en render en lugar de guardar

Una derivación es un valor que se obtiene a partir de props/estado actuales: totales, filtros, conteos, validaciones, textos formateados, etc. Guardar derivaciones en estado suele crear duplicación.

Ejemplo: total y validación derivados

Supón un carrito con cantidades. En vez de tener total en estado, calcúlalo desde items.

function CartSummary({ items }) {  const total = items.reduce((acc, item) => acc + item.price * item.qty, 0);  const isEmpty = items.length === 0;  return (    <section>      <p>Total: ${total}</p>      {isEmpty ? <p>Tu carrito está vacío</p> : null}    </section>  );}

Esto mantiene el componente previsible: el total siempre coincide con los items actuales.

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

Guía práctica: ¿esto debe ser estado o derivación?

PreguntaSi la respuesta es “sí”Entonces
¿Se puede calcular a partir de props/estado existentes?Derívalo en render (o en una función auxiliar).
¿Representa algo que el usuario puede cambiar directamente?Probablemente es estado.
¿Necesitas conservarlo entre renders aunque las entradas cambien?Probablemente es estado (o un efecto/almacenamiento externo).

Props hacia abajo, eventos hacia arriba

Cuando un componente hijo necesita “pedir” un cambio, no modifica el estado del padre directamente. En su lugar, el padre pasa una función (callback) y el hijo la ejecuta ante un evento.

Diagrama conceptual: datos bajan, eventos suben

Padre (estado)  |  props (datos, callbacks)  vHijo (UI)  |  onClick/onChange llama callback  vPadre actualiza estado

Este patrón mantiene el control del estado donde vive la fuente de verdad, y hace que los hijos sean más reutilizables.

Elevación de estado: cuando varios componentes lo necesitan

Si dos o más componentes necesitan leer y/o actualizar el mismo dato, ese estado debe vivir en su ancestro común más cercano. A esto se le llama elevar estado.

Diagrama conceptual para decidir ubicación del estado

          Ancestro común (¿quién necesita el dato?)                 /                 \        Componente A           Componente B        (lee/actualiza)       (lee/actualiza)

Regla práctica: coloca el estado en el componente más bajo del árbol que aún pueda abastecer a todos los consumidores necesarios.

Checklist para ubicar el estado

  • ¿Quién lo muestra? (componentes que lo leen)
  • ¿Quién lo cambia? (componentes que disparan eventos)
  • ¿Cuál es el ancestro común más cercano entre lectores y escritores?
  • ¿Se puede dividir en estados independientes? (evita “mega-estados” innecesarios)

Refactor 1: de estados duplicados a uno único (elevación)

Escenario: un buscador y una lista necesitan compartir el texto de búsqueda. Un error típico es que cada uno tenga su propio estado, quedando desincronizados.

Antes (duplicado): cada componente con su propio estado

function SearchBox() {  const [query, setQuery] = useState("");  return <input value={query} onChange={(e) => setQuery(e.target.value)} />;}function ResultsList({ items }) {  const [query, setQuery] = useState("");  const filtered = items.filter((x) => x.name.includes(query));  return (    <div>      <input value={query} onChange={(e) => setQuery(e.target.value)} />      <ul>{filtered.map((x) => <li key={x.id}>{x.name}</li>)}</ul>    </div>  );}

Problema: hay dos query distintos. Cambiar uno no afecta al otro.

Después (único): estado en el ancestro común

function SearchPage({ items }) {  const [query, setQuery] = useState("");  const filtered = items.filter((x) => x.name.toLowerCase().includes(query.toLowerCase()));  return (    <div>      <SearchBox query={query} onQueryChange={setQuery} />      <ResultsList items={filtered} />    </div>  );}function SearchBox({ query, onQueryChange }) {  return (    <input      value={query}      onChange={(e) => onQueryChange(e.target.value)}      placeholder="Buscar..."    />  );}function ResultsList({ items }) {  return <ul>{items.map((x) => <li key={x.id}>{x.name}</li>)}</ul>;}

Ahora hay una sola fuente de verdad (query) y la lista siempre refleja el input.

Paso a paso para aplicar elevación de estado

  • Identifica el dato compartido (por ejemplo, query).
  • Encuentra el ancestro común más cercano de los componentes que lo usan.
  • Mueve el useState a ese ancestro.
  • Pasa el valor hacia abajo por props.
  • Pasa callbacks hacia abajo para que los hijos puedan solicitar cambios.
  • Elimina estados duplicados en los hijos.

Refactor 2: derivar valores en render en vez de sincronizarlos

Otro caso común: guardar en estado un valor que es calculable, y luego “sincronizarlo” con efectos. Eso añade complejidad y riesgo de bucles o desajustes.

Antes (sincronización innecesaria)

function PricePanel({ price, qty }) {  const [total, setTotal] = useState(0);  useEffect(() => {    setTotal(price * qty);  }, [price, qty]);  return <p>Total: ${total}</p>;}

Después (derivación directa)

function PricePanel({ price, qty }) {  const total = price * qty;  return <p>Total: ${total}</p>;}

Menos piezas móviles: el total siempre es correcto y no depende de sincronización.

Refactor 3: mover lógica fuera del JSX para mejorar legibilidad

Cuando el JSX acumula condiciones, mapeos y formateos, se vuelve difícil de leer. Una mejora simple es extraer cálculos y decisiones a variables o funciones auxiliares.

Antes (JSX cargado)

function UserBadge({ user }) {  return (    <div>      <h3>        {user.firstName} {user.lastName}        {user.isPro ? " (PRO)" : ""}      </h3>      <p>        {user.lastLogin          ? `Último acceso: ${new Date(user.lastLogin).toLocaleDateString()}`          : "Nunca ha iniciado sesión"}      </p>    </div>  );}

Después (cálculos arriba, JSX declarativo)

function UserBadge({ user }) {  const fullName = `${user.firstName} ${user.lastName}`;  const proLabel = user.isPro ? " (PRO)" : "";  const lastLoginText = user.lastLogin    ? `Último acceso: ${new Date(user.lastLogin).toLocaleDateString()}`    : "Nunca ha iniciado sesión";  return (    <div>      <h3>{fullName}{proLabel}</h3>      <p>{lastLoginText}</p>    </div>  );}

El JSX queda como una “plantilla” clara, y la lógica queda en un bloque fácil de testear mentalmente.

Separar lógica de negocio simple en funciones auxiliares

Una forma práctica de mantener componentes previsibles es extraer lógica pura (sin efectos, sin acceso al DOM, sin mutaciones) a funciones auxiliares. Esto reduce el ruido en el componente y facilita reutilización.

Criterios para extraer a una función

  • Es una transformación pura: mismo input, mismo output (por ejemplo, filtrar, ordenar, formatear).
  • Se repite en más de un componente.
  • Hace el render más legible al sacar detalles del JSX.
  • No necesita estado: solo usa argumentos.

Ejemplo: filtrar y ordenar productos con funciones puras

function normalize(text) {  return text.trim().toLowerCase();}function filterProducts(products, query) {  const q = normalize(query);  if (!q) return products;  return products.filter((p) => normalize(p.name).includes(q));}function sortProductsByPrice(products, direction) {  const factor = direction === "asc" ? 1 : -1;  return [...products].sort((a, b) => (a.price - b.price) * factor);}
function ProductsPage({ products }) {  const [query, setQuery] = useState("");  const [direction, setDirection] = useState("asc");  const visible = sortProductsByPrice(filterProducts(products, query), direction);  return (    <div>      <input value={query} onChange={(e) => setQuery(e.target.value)} />      <select value={direction} onChange={(e) => setDirection(e.target.value)}>        <option value="asc">Precio: menor a mayor</option>        <option value="desc">Precio: mayor a menor</option>      </select>      <ul>        {visible.map((p) => (          <li key={p.id}>{p.name} - ${p.price}</li>        ))}      </ul>    </div>  );}

Observa que el componente solo orquesta estado y render; la lógica de negocio simple vive en funciones puras.

Mapa mental de decisiones: estado, derivación o prop

¿El dato cambia con interacción o tiempo?  ├─ No: probablemente es prop o constante  └─ Sí: ¿lo usan varios componentes?       ├─ No: estado local en el componente que lo usa       └─ Sí: elevar al ancestro común más cercano¿Se puede calcular desde otro estado/prop?  ├─ Sí: derivación (no guardarlo en estado)  └─ No: estado (fuente de verdad)

Aplicando estas reglas, reduces duplicación, mantienes un flujo de datos claro y haces que los componentes sean más fáciles de mantener.

Ahora responde el ejercicio sobre el contenido:

En una pantalla con un cuadro de búsqueda y una lista de resultados que deben reflejar el mismo texto, ¿cuál es la forma recomendada de manejar ese dato en React para evitar estados duplicados?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Si varios componentes leen y/o actualizan el mismo dato, debe vivir en el ancestro común más cercano como única fuente de verdad. El padre pasa props hacia abajo y un callback para que el hijo dispare cambios, evitando duplicación y desincronización.

Siguiente capítulo

Estructura de proyecto en React: modularidad, organización y mantenibilidad

Arrow Right Icon
Portada de libro electrónico gratuitaReact para principiantes: mentalidad de componentes y manejo de estado
75%

React para principiantes: mentalidad de componentes y manejo de estado

Nuevo curso

12 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.