React UseState dispatch

El hook de useState nos ofrece una función para setear el nuevo estado (un dispatcher), esta acción de setear un valor es asícnrona, es decir justo en la siguiente line de ejecucíon de un set de estado no tengamos el nuevo valor disponible, seguramente habrá que esperar a que se tire un nuevo render.

Esto nos puede llevar a casos extraños si trabajamos con asincronía (por ejemnplo si usamos un fetch para pedir datos a servidor o si usamos un setTimeout)

Dispatch function updates

Vamos a ver el problema en acción con un ejemplo, para ello utilizaremos set timeout:

En este código estamos incrementado el valor de la variable contador y un segundo después volvemos a incrementarlo.

import "./styles.css";
import React from "react";

export default function App() {
  const [contador, setContador] = React.useState(0);

  React.useEffect(() => {
    setContador(1);
    setTimeout(() => {
      setContador(contador + 1);
    }, 500);
  }, []);

  return (
    <div className="App">
      <h1>Valor: {contador}</h1>
    </div>
  );
}

¿Qué ocurre si ejecutamos esto? Que valor siempre vale uno ¿Cooomooor?

  • En el primer set el valor de la variable contador para esa invocación de la función es cero (en siguientes invocaciones cuando se haga un rerender valdrá 1, pero eso será en otra invocación de la función)
  • Dentro del timeout, aunque hayan pasado los 500 milisegundos y el 1 este asignado en el estado, la variable contador seguira valiendo cero, y que el código que se ejecuta dentro del setTimeout apunta a la variable antigua (const contador), para saber mejor como funciona esto mi consejo aquí es que revises el principio de closure.

¿Qué podemos hacer para asegurarnos que estamos manejando el valor más actual de este estado? Podemos usar una función dentro del set del hook:

import "./styles.css";
import React from "react";

export default function App() {
  const [contador, setContador] = React.useState(0);

  React.useEffect(() => {
    setContador(1);
    setTimeout(() => {
-      setContador(contador + 1);
+      setContador((contadorActual) => contadorActual + 1);
    }, 500);
  }, []);

  return (
    <div className="App">
      <h1>
        Valor: {contador}
      </h1>
    </div>
  );
}

De esta manera si nos aseguramos que tenemos el último valor disponible, ahora si ejecutamos podemos ver como valor empieza valiendo 1, y después pasa 2 .

¿Te apuntas a nuestro máster?

Si te ha gustado este ejemplo y tienes ganas de aprender Front End guiado por un grupo de profesionales ¿Por qué no te apuntas a nuestro Máster Front End Online Lemoncode? Tenemos tanto edición de convocatoria con clases en vivo, como edición continua con mentorización, para que puedas ir a tu ritmo y aprender mucho.