Saltar al contenido
Under the Hood
Available in Spanish only

React

¿De quién es el dato? Props vs State en React

Última revisión:

React
  • Conceptos básicos
  • Estado

Respuesta corta

Ambos son datos útiles para el componente; sin embargo, las props llegan desde el padre, siendo de solo lectura para el componente que las recibe. En cambio, el state nace dentro del componente y solo él puede cambiarlo.

Ambos pueden ser modificados con el tiempo: la diferencia es quién es el dueño del dato. Muchas veces, el state de un padre es la prop de su hijo.

En esta página

Explicación

Quién es dueño del dato

Las props son datos que llegan desde fuera del componente, de padre a hijo, como argumento de la función. Sirven para configurarlo o pasarle lo que necesita, y para quien las recibe son de solo lectura.

El state es interno: se crea, se inicializa y se gestiona dentro del componente, por ejemplo con useState. Guarda los datos que cambian con el tiempo o con la interacción del usuario.

Props State
De dónde viene Del padre Del propio componente
Quién puede cambiarlo Solo el padre Solo el componente, con su setter
¿Cambia con el tiempo? Sí, si el padre pasa otro valor Sí, cuando se llama al setter

Cuando cambia el state, el componente se vuelve a renderizar. Y cuando un padre se renderiza, sus hijos también, salvo optimizaciones como memo.

Las props bajan

Las props siempre van de padre a hijo. Quien las recibe no las modifica, pero eso no significa que nunca cambien: si el padre se renderiza con otro valor, el hijo recibe el nuevo. Por eso la diferencia real no es que una cambie y la otra no, sino quién es dueño del dato.

Como dato adicional, Context también lleva datos hacia abajo sin pasar por cada nivel, pero es otro mecanismo.

El state se reemplaza

El state cambia con el tiempo, pero no se muta: se reemplaza con su setter. Si escribimos user.name = 'Ana', React no se entera y no vuelve a renderizar. Con setUser({ ...user, name: 'Ana' }) recibe un objeto nuevo y sí lo hace.

Los cambios suben

El state de un padre suele ser la prop de su hijo. Si el hijo necesita cambiar ese dato, no toca la prop: llama a una función que recibió por props, como onChange. El padre actualiza su state y el valor nuevo vuelve a bajar.

Si dos hermanos necesitan el mismo dato, el state sube a su padre común.

Cuál usar

Es importante preguntarnos esto, en orden:

  1. ¿Viene del padre? Es una prop.
  2. ¿Se puede calcular con lo que ya tenemos? Lo calculamos en el render, sin guardarlo.
  3. ¿Cambia y le pertenece a este componente? Es state.

Código

Veamos el mismo carrito en dos versiones. En la primera, CartItem copia la prop a su state. Ese valor inicial se usa solo en el primer render, así que quedan dos copias del dato: si Cart reinicia las cantidades, el hijo sigue mostrando la suya.

En la segunda, Cart es el único dueño. CartItem recibe la cantidad, avisa cuando cambia y el valor nuevo vuelve a bajar.

Antes (copia la prop)

CartItem.tsx
import { useState } from 'react';
type CartItemProps = {
name: string;
quantity: number;
};
export function CartItem({ name, quantity }: CartItemProps) {
// La prop solo se usa como valor inicial: después, count queda desconectado
const [count, setCount] = useState(quantity);
return (
<li>
{name}: {count}
<button onClick={() => setCount(count + 1)}>+</button>
</li>
);
}

Después (una sola fuente)

Cart.tsx
import { useState } from 'react';
type Item = { id: number; name: string; quantity: number };
type CartItemProps = {
name: string;
quantity: number;
onQuantityChange: (quantity: number) => void;
};
function CartItem({ name, quantity, onQuantityChange }: CartItemProps) {
return (
<li>
{name}: {quantity}
<button onClick={() => onQuantityChange(quantity + 1)}>+</button>
</li>
);
}
export function Cart({ initialItems }: { initialItems: Item[] }) {
const [items, setItems] = useState(initialItems);
function changeQuantity(id: number, quantity: number) {
// Un arreglo nuevo con un ítem nuevo: nada se muta
setItems(items.map((item) => (item.id === id ? { ...item, quantity } : item)));
}
return (
<ul>
{items.map((item) => (
<CartItem
key={item.id}
name={item.name}
quantity={item.quantity}
onQuantityChange={(quantity) => changeQuantity(item.id, quantity)}
/>
))}
</ul>
);
}

Una aclaración: en Cart, useState(initialItems) también usa una prop como valor inicial, pero aquí no es un error. El prefijo initial deja claro que la prop solo es el valor de partida y que, desde ahí, el dueño del dato es Cart.

Trade-offs

  • Lo que ganamos con las props. El componente es predecible, reutilizable y fácil de probar, porque su resultado depende de lo que recibe.
  • Lo que ganamos con el state local. Es simple y queda aislado, pero nadie más puede leerlo.
  • Lo que cuesta subir el estado. Hay más props que pasar y más componentes que se vuelven a renderizar.
  • La regla práctica. Dejamos el estado lo más abajo posible y lo subimos solo cuando otro componente lo necesita.
  • El error típico. Mutar el estado directamente, o copiar una prop al state cuando se podía usar tal cual.

Entradas relacionadas

  • React

    ¿Qué es un componente? Lo aplicamos en React y Blazor

    Un componente es una pieza encapsulada y reutilizable de la interfaz: recibe datos desde afuera, puede tener estado propio y describe cómo debe verse la UI para esos datos. Renderizarlo es ejecutarlo para obtener esa descripción; luego el framework la compara con la anterior y actualiza en el DOM solo lo que cambió.

    General
  • React

    Hooks de React: cómo un componente recuerda entre renders

    Los hooks son funciones que se enganchan a React para que un componente de función recuerde valores entre renders, lea contexto o se sincronice con algo externo, cosas que antes solo podían hacer las clases. Se llaman siempre en el mismo orden y en el nivel superior del componente, porque React los identifica por ese orden.

    React
  • React

    useEffect() vs useMemo(): ¿cuándo y cómo se elige cada uno?

    useEffect() sincroniza el componente con algo externo después del render y no produce un valor para mostrar. useMemo() calcula un valor durante el render y lo guarda en caché. Si un dato se puede derivar de props o estado, se calcula en el render, nunca con un efecto.

    React