Estado en React: modelado de datos cambiantes y renderizado reactivo

Capítulo 4

Tiempo estimado de lectura: 8 minutos

+ Ejercicio

Estado: la fuente de verdad local

En React, el estado representa datos que cambian en el tiempo y que afectan lo que se renderiza. Piensa en el estado como la fuente de verdad local de un componente: cuando el estado cambia, React vuelve a ejecutar el render del componente para reflejar ese cambio en la UI.

El objetivo no es “guardar todo en estado”, sino modelar el mínimo estado necesario para que la interfaz sea consistente y fácil de mantener.

¿Qué debe ser estado y qué no?

Antes de crear un useState, clasifica el dato en una de estas categorías:

  • Constante: no cambia durante la vida del componente (o viene de un módulo). Ej.: lista fija de países, configuración de columnas.
  • Derivado: se puede calcular a partir de props o estado. No lo guardes en estado; calcúlalo en el render (o memorízalo si es costoso).
  • Estado: cambia por interacción del usuario, por eventos del sistema o por respuestas de red, y necesitas que el render lo refleje.

Regla práctica: si puedes obtener un valor con una función pura a partir de otros valores que ya tienes, probablemente es derivado.

Ejemplo: estado vs. derivado

import { useState } from "react";export function CartSummary({ items }) {  // items viene por props  const total = items.reduce((acc, it) => acc + it.price * it.qty, 0); // derivado  return <p>Total: ${total}</p>;}

Aquí total no debe ser estado: se calcula desde items. Guardarlo en estado introduciría riesgo de desincronización.

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

Renderizado reactivo: qué pasa cuando cambia el estado

Cuando llamas al setter de estado (por ejemplo setCount), React programa una actualización. En el siguiente render, el componente se ejecuta de nuevo con el nuevo estado y React actualiza el DOM donde haga falta.

Dos ideas clave

  • El render debe ser una función pura del estado y props: mismo input, mismo output.
  • No leas el DOM para “saber” el estado. El estado vive en React; el DOM es una proyección.

Guía práctica: modelar estado mínimo (paso a paso)

Paso 1: lista lo que puede cambiar

Escribe los datos que cambian: texto de un input, ítem seleccionado, si un panel está abierto, datos cargados, errores, etc.

Paso 2: elimina lo que sea constante

Si no cambia, no es estado. Déjalo como constante o prop.

Paso 3: elimina lo derivado

Si se calcula desde otros valores, no lo guardes. Ej.: filteredItems se calcula desde items y query.

Paso 4: decide la forma del estado

Elige una estructura que haga fáciles las actualizaciones. Preferible: simple, normalizada, sin duplicación.

Paso 5: define actualizaciones inmutables

En React se actualiza estado de forma inmutable: no mutas el objeto/array existente; creas uno nuevo con los cambios.

Evitar inconsistencias: no dupliques estado

Un problema común es guardar el mismo “hecho” en dos sitios. Ejemplo típico: guardar items y también total en estado. Si actualizas uno y olvidas el otro, la UI queda inconsistente.

Mal (duplicado)Bien (única fuente de verdad)
const [items, setItems] = useState([]); const [total, setTotal] = useState(0);const [items, setItems] = useState([]); const total = items.reduce(...);

Normalización básica de estructuras

Cuando el estado representa colecciones, suele ser más fácil mantenerlo si lo normalizas. En vez de arrays anidados difíciles de actualizar, separa:

  • Diccionario por id para acceso/actualización rápida.
  • Array de ids para orden.
const initial = {  byId: {    "p1": { id: "p1", name: "Teclado", price: 30 },    "p2": { id: "p2", name: "Mouse", price: 15 }  },  allIds: ["p1", "p2"]};

Esto reduce la duplicación y hace más predecibles las actualizaciones.

Actualización inmutable de objetos y arrays

Objetos: copiar y sobrescribir

const [profile, setProfile] = useState({ name: "Ana", city: "Lima" });function updateCity(newCity) {  setProfile(prev => ({ ...prev, city: newCity }));}

Arrays: crear un nuevo array

  • Agregar: [...prev, newItem]
  • Eliminar: prev.filter(...)
  • Actualizar: prev.map(...)
const [todos, setTodos] = useState([{ id: 1, text: "Leer", done: false }]);function toggleTodo(id) {  setTodos(prev =>    prev.map(t => (t.id === id ? { ...t, done: !t.done } : t))  );}function removeTodo(id) {  setTodos(prev => prev.filter(t => t.id !== id));}function addTodo(text) {  setTodos(prev => [...prev, { id: crypto.randomUUID(), text, done: false }]);}

Nota: usar la forma funcional setState(prev => ...) evita errores cuando varias actualizaciones ocurren seguidas.

Escenario 1: toggles (UI booleana)

Un toggle es el ejemplo más directo de estado: un booleano que controla el render.

import { useState } from "react";export function NewsletterToggle() {  const [subscribed, setSubscribed] = useState(false);  return (    <div>      <label>        <input          type="checkbox"          checked={subscribed}          onChange={e => setSubscribed(e.target.checked)}        />        Suscribirme      </label>      {subscribed && <p>Gracias por suscribirte.</p>}    </div>  );}

Escenario 2: formularios (inputs controlados)

En formularios, el estado suele ser el valor del input. Un patrón común es un objeto form con campos, actualizado de forma inmutable.

Formulario simple paso a paso

  • Define el estado inicial.
  • Vincula value y onChange (input controlado).
  • Valida y envía usando el estado como fuente de verdad.
import { useState } from "react";export function SignupForm() {  const [form, setForm] = useState({ email: "", password: "" });  const [error, setError] = useState(null);  function onChange(e) {    const { name, value } = e.target;    setForm(prev => ({ ...prev, [name]: value }));  }  function onSubmit(e) {    e.preventDefault();    if (!form.email.includes("@")) {      setError("Email inválido");      return;    }    setError(null);    // enviar form.email y form.password  }  return (    <form onSubmit={onSubmit}>      <input        name="email"        value={form.email}        onChange={onChange}        placeholder="Email"      />      <input        name="password"        type="password"        value={form.password}        onChange={onChange}        placeholder="Contraseña"      />      {error && <p>{error}</p>}      <button type="submit">Crear cuenta</button>    </form>  );}

Evita guardar en estado cosas como “isEmailValid” si se puede derivar de form.email. Puedes calcularlo en el render:

const isEmailValid = form.email.includes("@");

Escenario 3: selección en listas

Para selección, es común guardar el id seleccionado, no el objeto completo (evita duplicación y facilita sincronía si la lista cambia).

import { useState } from "react";export function ProductList({ products }) {  const [selectedId, setSelectedId] = useState(null);  const selected = products.find(p => p.id === selectedId) ?? null; // derivado  return (    <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>      <ul>        {products.map(p => (          <li key={p.id}>            <button onClick={() => setSelectedId(p.id)}>              {p.name}            </button>          </li>        ))}      </ul>      <div>        {selected ? (          <div>            <h3>{selected.name}</h3>            <p>Precio: ${selected.price}</p>          </div>        ) : (          <p>Selecciona un producto</p>        )}      </div>    </div>  );}

Si guardas el objeto seleccionado completo en estado y luego products se actualiza desde fuera, puedes quedarte con una versión vieja. Guardar el id reduce ese riesgo.

Escenario 4: carga de datos (estado de red)

Cuando cargas datos, normalmente necesitas representar al menos: datos, estado de carga y error. Mantén el modelo simple y explícito.

import { useEffect, useState } from "react";export function Users() {  const [users, setUsers] = useState([]);  const [status, setStatus] = useState("idle"); // "idle" | "loading" | "success" | "error"  const [error, setError] = useState(null);  useEffect(() => {    let cancelled = false;    async function load() {      setStatus("loading");      setError(null);      try {        const res = await fetch("/api/users");        if (!res.ok) throw new Error("Error HTTP");        const data = await res.json();        if (!cancelled) {          setUsers(data);          setStatus("success");        }      } catch (e) {        if (!cancelled) {          setError(e.message);          setStatus("error");        }      }    }    load();    return () => {      cancelled = true;    };  }, []);  if (status === "loading") return <p>Cargando...</p>;  if (status === "error") return <p>{error}</p>;  return (    <ul>      {users.map(u => (        <li key={u.id}>{u.name}</li>      ))}    </ul>  );}

Evita estados redundantes como isLoading y status a la vez. Elige uno (por ejemplo status) para que no haya combinaciones inválidas.

Patrones para evitar errores comunes

1) No mutar estado existente

Esto es un error típico:

// NO: mutación directaconst [items, setItems] = useState([]);function add(item) {  items.push(item);  setItems(items);}

La versión correcta crea un nuevo array:

function add(item) {  setItems(prev => [...prev, item]);}

2) Cuidado con “estado espejo”

“Estado espejo” es copiar una prop a estado sin necesidad. Si una prop ya es la fuente de verdad, duplicarla en estado suele causar desincronización. Si necesitas editar localmente un valor que viene de fuera (por ejemplo, un formulario de edición), define claramente el momento en que inicializas el estado local (por ejemplo, al abrir un modal) y evita mantener dos fuentes de verdad activas para lo mismo.

3) Mantén el estado lo más cercano posible a donde se usa

Si solo un componente necesita un dato, mantenlo local. Si varios componentes lo necesitan, sube la fuente de verdad al ancestro común (para no duplicar estado en ramas distintas).

4) Prefiere ids y datos normalizados para colecciones

Guardar objetos completos en varios lugares aumenta el riesgo de inconsistencias. Guardar ids y derivar el objeto desde una colección central suele ser más estable.

Ahora responde el ejercicio sobre el contenido:

En un componente que muestra una lista de productos y permite seleccionar uno, ¿cuál es la forma más recomendable de modelar el estado para evitar duplicación e inconsistencias cuando la lista cambia?

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

¡Tú error! Inténtalo de nuevo.

Guardar solo el id evita duplicar el mismo “hecho” en dos lugares. Así, el objeto seleccionado se deriva desde la lista actual y se reduce el riesgo de quedar con una versión desactualizada si la colección cambia.

Siguiente capítulo

Eventos en React: interacción del usuario y actualización segura del estado

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

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.