Ir al contenido
wannacode

Estado

En resumen: El estado es un valor que un componente recuerda entre renders; en React se guarda con useState, y cada cambio vuelve a renderizar el componente.

Problema

El botón no cuenta

La tarjeta del producto tiene un botón «Añadir» y un contador «En el carrito». Pulsa «Añadir» tres veces.

React

La consola dice count = 1, 2, 3. La tarjeta dice 0. No hay ningún error. La variable count cambia, pero la pantalla no la sigue. ¿Dónde está la diferencia?

La idea

Una variable normal no basta

La tarjeta no la dibuja tu código, sino la biblioteca. Solo vuelve a dibujar cuando sabe que algo cambió. count++ cambia la variable, pero no avisa a nadie.

ProductCard es una función. React la llama cuando dibuja la tarjeta, y ella devuelve lo que hay que mostrar. let count = 0 es una variable local de esa llamada. El clic la cambia, pero nadie vuelve a llamar a la función, así que la tarjeta se queda en 0.

Hay un segundo problema. Si React vuelve a llamar a la función por otro motivo, la primera línea es otra vez let count = 0. Todo lo que se había acumulado en la variable se pierde.

Necesitas datos que el componente recuerde entre dos renders y cuyo cambio lo vuelva a dibujar. Esto se llama estado.

La idea

useState y ref

const [count, setCount] = useState(0);
function add() {
setCount(count + 1);
}
  • useState(0) devuelve dos cosas: el valor y la función que lo cambia. El cero es solo el valor inicial.
  • count es el valor de este render.
  • setCount(1) recuerda el valor nuevo y le dice a React que vuelva a dibujar la tarjeta. React llama otra vez a ProductCard, y esta vez useState devuelve 1.

useState es un hook: una función de React que se llama al principio del componente, nunca dentro de un if ni de un bucle. React distingue los estados por el orden en que se llaman.

El manejador del clic se da como función: onClick={add}. Si escribes onClick={add()}, add se llama mientras se dibuja la tarjeta. Cambia el estado, que dibuja otra vez, y React se detiene con el error «Too many re-renders».

React

Míralo

Mira qué pasa con un clic

function ProductCard({ product }) {
const [count, setCount] = useState(0);
function addThree() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}
return (
<article>
<button onClick={addThree}>+3</button>
<p>En el carrito: {count}</p>
</article>
);
}
Paso a paso, en palabras
  1. La tarjeta guarda count en el estado de React: useState(0) da el valor y la función que lo cambia.
  2. React llama a la función ProductCard. No toca la página, sino que devuelve una descripción: un botón y un texto con count = 0.
  3. La primera vez, la descripción se convierte en elementos reales en el DOM. La página ya muestra «En el carrito: 0».
  4. Un clic en +3: el navegador llama a addThree.
  5. Tres veces setCount(count + 1). En esta llamada count es una instantánea: 0. Cada vez va a la cola «pasa a 1». La pantalla todavía no cambia.
  6. El manejador terminó. React aplica la cola: 1, 1, 1 da 1. Luego vuelve a llamar a ProductCard, y ahora count es 1.
  7. React compara la descripción nueva con la vieja. El botón es el mismo, solo cambió el texto. En el DOM cambia solo el texto.
  8. Por eso el contador muestra 1, no 3. Cuando el valor nuevo depende del viejo, pasa una función: setCount((c) => c + 1).

La tarjeta no se construye de nuevo. La biblioteca compara la descripción nueva con la vieja y cambia en el DOM solo la diferencia: el texto. El botón es el mismo elemento y el foco se queda en él. Esa es la diferencia con render() del módulo del DOM, que lo reemplazaba todo.

Pruébalo

Arregla el contador

Esta es la tarjeta del principio. Haz que count sea estado, para que después de cada clic en «Añadir» la tarjeta muestre el número nuevo.

React

La idea

El valor es una instantánea

El agua mineral también se vende en un pack de 6 botellas. Sería cómodo que el botón del pack llamara seis veces a add(). Aquí las dos bibliotecas se comportan de forma distinta.

count es una constante de un render. setCount no cambia count en el manejador que se está ejecutando ahora. Pone una petición en una cola, y React la aplica después del manejador:

function addBox() {
for (let i = 0; i < 6; i++) {
setCount(count + 1); // 0 + 1, seis veces: «pasa a 1»
}
}

Las seis peticiones son «pasa a 1» y el contador pasa a 1, no a 6. Di en su lugar «suma 1 al último valor»: pasa una función en vez de un valor.

setCount((c) => c + 1);

React llama a cada una de esas funciones con el resultado de la anterior: 0 → 1 → 2 … → 6. La regla: cuando el valor nuevo depende del viejo, pasa una función.

Pruébalo

Encuentra el error

«Añadir un pack» debería añadir 6 botellas. ¿Qué línea tiene la culpa?

Elige la línea con el error.

Pruébalo

Un pack de agua

Escribe addBox() para que «Añadir un pack (6 uds.)» sume 6 botellas a lo que ya hay en el carrito. La comprobación pulsa «Añadir» una vez, luego el pack dos veces, y espera 1, 7 y 13.

React

La idea

Cada tarjeta recuerda lo suyo

El estado pertenece a la instancia, no al código. El catálogo tiene dos tarjetas del mismo ProductCard, y cada una tiene su propio count:

React

Pulsa «Añadir» dos veces en el pan y una en la leche: 2 y 1. La biblioteca guarda el estado de cada tarjeta según su lugar en el árbol de componentes. Si quitas la tarjeta de la página, su estado se va con ella. Cuando dos tarjetas tienen que mostrar el mismo número, este tiene que vivir en su padre común. De eso trata la lección sobre estado compartido de este módulo.

Más precisamente, para curiosos

Por qué una instantánea. React no cambia count en el sitio, porque la función se llamó con ese valor, y todo lo que hay en ella, también los manejadores, ve ese valor. Así un render es coherente: todo lo que se calcula a partir de count se calcula a partir de un único valor.

Cuándo React no dibuja. Si setCount recibe el mismo valor (comparado con Object.is), React se salta el render. Esto importa en la lección siguiente, con los arrays.

Las actualizaciones se agrupan. Todas las llamadas a setCount en un manejador dan un solo render, y desde React 18 también dentro de setTimeout y después de await.

StrictMode. En desarrollo, <StrictMode> llama a cada componente dos veces, para detectar un componente que cambia algo fuera mientras se dibuja. El sandbox no lo usa, y un proyecto de Vite lo tiene en main.jsx.

Componentes de clase. El código antiguo de React escribe el componente como una clase con this.state y this.setState. El código nuevo usa funciones y hooks.

Ejercicio

Elige la cantidad

El catálogo tiene tres tarjetas. En cada una añade una cantidad que empieza en 1, un botón «−» (nombre para el lector de pantalla: «Menos Pan blanco»), un botón «+» («Más Pan blanco») y una línea «2 × 1,20 € = 2,40 €» con la cantidad, el precio y el total. «−» está desactivado cuando la cantidad es 1. Cada tarjeta cuenta por su cuenta.

React

Test

Ponte a prueba

1. ¿Por qué let count = 0 y count++ en el componente no cambian la pantalla?
2. ¿Qué hace setCount(5)?
3. count es 0. El manejador llama tres veces a setCount(count + 1). ¿Qué muestra la tarjeta?
4. ¿Por qué onClick={add()} es un error?
5. La página tiene dos ProductCard, cada una con su propio count. La clienta pulsa «Añadir» en la primera. ¿Qué pasa?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·