El estado como una instantánea

Las variables de estado pueden parecerse a las variables normales de JavaScript en las que se puede leer y escribir. Sin embargo, el estado se comporta más como una instantánea. Al asignarlo no se cambia la variable de estado que ya tienes, sino que se desencadena una rerenderizado.

Aprenderás

  • Cómo la asignación del estado desencadena los rerenderizados.
  • Cuándo y cómo se actualiza el estado.
  • Por qué el estado no se actualiza inmediatamente después de asignarlo.
  • Cómo los controladores de eventos acceden a una “instantánea” del estado.

La asignación de estado desencadena renderizados

Puedes hacerte la idea de tu interfaz de usuario como una que cambia directamente al evento del usuario como un clic. En React, funciona un poco diferente de este modelo mental. En la página anterior, viste que al asignar estado se solicita un rerenderizado de React. Esto significa que para que una interfaz reaccione al evento, es necesario actualizar el estado.

En este ejemplo, al pulsar “Enviar”, setIsSent(true) indica a React que vuelva a renderizar la UI:

import { useState } from 'react';

export default function Form() {
  const [isSent, setIsSent] = useState(false);
  const [message, setMessage] = useState('¡Hola!');
  if (isSent) {
    return <h1>¡Tu mensaje está en camino!</h1>
  }
  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      setIsSent(true);
      sendMessage(message);
    }}>
      <textarea
        placeholder="Mensaje"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Enviar</button>
    </form>
  );
}

function sendMessage(message) {
  // ...
}

Esto es lo que ocurre cuando se hace clic en el botón:

  1. Se ejecuta el controlador de evento onSubmit.
  2. setIsSent(true) asigna isSent a true y pone en cola un nuevo renderizado.
  3. React vuelve a renderizar el componente según el nuevo valor de isSent.

Veamos con más detalle la relación entre estado y renderizado.

El renderizado toma una instantánea en el tiempo

“Renderizado” significa que React está llamando a tu componente, que es una función. El JSX que devuelves de esa función es como una instantánea de la UI en el tiempo. Tus props, controladores de eventos y variables locales fueron todos calculados usando su estado en el momento del renderizado.

A diferencia de una fotografía o un fotograma de una película, la “instantánea” de la interfaz de usuario que devuelves es interactiva. Incluye lógica como controladores de eventos que especifican lo que sucede en respuesta a las entradas. React entonces actualiza la pantalla para que coincida con esta instantánea y conecta los controladores de eventos. Como resultado, al pulsar un botón se activará el controlador de clic de tu JSX.

Cuando React vuelve a renderizar un componente:

  1. React vuelve a llamar a tu función.
  2. Tu función devuelve una nueva instantánea JSX.
  3. React entonces actualiza la pantalla para que coincida con la instantánea devuelta por tu función.
  1. React ejecuta la función
  2. Calcula la instantánea
  3. Actualiza el árbol del DOM

Ilustrado por Rachel Lee Nabors

Como memoria de un componente, el estado no es como una variable regular que desaparece después de que tu función devuelva un valor. El estado en realidad “vive” en el propio React -como si estuviera en una estantería- fuera de tu función. Cuando React llama a tu componente, te da una instantánea del estado para ese renderizado en particular. Tu componente devuelve una instantánea de la interfaz de usuario con un nuevo conjunto de accesorios y controladores de eventos en su JSX, todo calculado usando los valores de estado de ese renderizado.

  1. Le dices a React que actualice el estado
  2. React actualiza el valor del estado
  3. React pasa una instantánea del valor del estado al componente

Ilustrado por Rachel Lee Nabors

He aquí un pequeño experimento para mostrarte cómo funciona esto. En este ejemplo, se podría esperar que al hacer clic en el botón “+3” se incrementara el contador tres veces porque se llama a setNumber(number + 1) tres veces.

Mira lo que ocurre cuando haces clic en el botón “+3”:

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);
      }}>+3</button>
    </>
  )
}

Observa que number sólo se incrementa una vez por clic.

La asignación del estado sólo lo cambia para el siguiente renderizado. Durante el primer renderizado, number era 0. Es por eso que en el controlador onClick de ese renderizado el valor de number sigue siendo 0, incluso después de que se llamara setNumber(number + 1):

<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>

Esto es lo que el controlador de clic de este botón le dice a React que haga:

  1. setNumber(number + 1): number es 0 así que setNumber(0 + 1).
    • React se prepara para el cambiar number a 1 en el siguiente renderizado.
  2. setNumber(number + 1): number es 0 así que setNumber(0 + 1).
    • React se prepara para el cambiar number a 1 en el siguiente renderizado.
  3. setNumber(number + 1): number es 0 así que setNumber(0 + 1).
    • React se prepara para el cambiar number a 1 en el siguiente renderizado.

Aunque hayas llamado a setNumber(number + 1) tres veces, en el controlador de evento de ese renderizado number es siempre 0, por lo que asignas el estado a 1 tres veces. Por eso, una vez que el controlador de evento termina, React vuelve a renderizar el componente con number igual a 1 en lugar de 3.

También puedes visualizarlo sustituyendo mentalmente las variables de estado por sus valores en tu código. Haciendo que la variable de estado number sea 0 para ese renderizado, tu controlador de evento se ve así:

<button onClick={() => {
setNumber(0 + 1);
setNumber(0 + 1