Hook useState

Cuando empezamos a estudiar los React Hooks por lo general el primer Hook que aprendemos es useState, este Hook nos te permite añadir el estado de React a un componente funcional.
En este articulo vamos a conocer todos los secretos que esconde el Hook useState, entraremos en detalle sobre su estructura y conoceremos las mejores practicas para usarlo correctamente y evitar los errores comunes.

Definición del Hook useState

El Hook useState es un Hook que permite añadir el estado de React a un componente funcional y sirve para manejar el estado de los elementos de un componente de manera que permite actualizar el estado de una variable y solo renderizar el tag html donde se lo use, de manera que cuando el estado cambia el componente responde volviendo a renderizar solo la parte del código afectada por la variable de estado mantenida por el hook useState.
Un ejemplo de un componente funcional que usa el Hook useState es el siguiente:

// Importamos el Hook useState desde React que nos permite mantener un estado local en un componente de función.
import React, { useState } from 'react'; 

function Contador() { 
// Declaramos una nueva variable de estado llamando al Hook useState, la cual llamaremos “count” 
const [count, setCount] = useState(0); 
return ( 
	<div> 
		<h1> El botón ha sido pulsado {count} veces </h1> 
		// Llamamos a setCount con un nuevo valor y React actualiza el componente
		<button onClick={() => setCount(count + 1)} > 
			Púlsame 
		</button> 
	</div> 
	)
};
La forma de declarar un hook de estado es:
const [count, setCount] = useState(0)
Donde:
  • count:Es una constante que representa el valor de estado actual del hook.
  • setCount:Método para actualizar el estado, lo que se haga aquí va a definir el nuevo valor del estado.
  • useState(0):Donde 0 es el valor inicial del estado. Todo hook de estado tiene que tener un valor inicial (en este caso es 0) y luego se ira actualizando.
Nota:

Cuando declaramos el hook como vimos anteriormente: const [count, setCount] = useState(0), debemos considerar que la parte de la izquierda es una desestructuración de la función useState (recordemos que un hook es una función). Si no esta familiarizado con el concepto se recomienda leer el siguiente articulo: Destructuring en JavaScript.

Cada vez que se llama a la función del segundo argumento (en este caso setCount) se cambia el valor del estado por lo tanto React vuelve a ejecutar la renderizacion del componente, solo se actualiza la parte de código del componente donde se esta usando el valor del estado (esto es muy importante para renderizado condicional) y ahí radica la potencia de React en que solo renderiza la parte que esta manejada por un estado sin necesidad de renderizar toda la pagina.

El cambio de valor de estado se puede hacer directamente sobre la constante recuperada por la desestructuración (en este ejemplo count)
setCounter ( counter +1 );
o bien por medio de un callback que devuelve un resultado es cual implícitamente actualiza el estado sin hacer referencia a la constante del useState
setCounter ( (c) => c + 1);

Hasta aquí vimos como se declara un hook useState, ahora vamos a contestar algunas preguntas que pueden ayudaran a comprender su funcionamiento en profundidad:

¿Qué hace la llamada a useState? Declara una “variable de estado”. Nuestra variable se llama count, pero podemos llamarla como queramos. Esta es una forma de “preservar” algunos valores entre las llamadas de la función. Normalmente, las variables “desaparecen” cuando se sale de la función, pero las variables de estado son conservadas por React.

¿Qué pasamos a useState como argumento? El único argumento para el Hook useState() es el estado inicial. Al el estado no tiene por qué ser un objeto, podemos usar números o strings si es todo lo que necesitamos. En nuestro ejemplo, solamente queremos un número para contar el número de clicks del usuario, por eso pasamos 0 como estado inicial a nuestra variable. (Si queremos guardar dos valores distintos en el estado, llamaríamos a useState() dos veces).

¿Qué devuelve useState? Devuelve una pareja de valores: el estado actual y una función que lo actualiza.

¿Por qué useState no se llama createState? “Crear” no sería del todo correcto porque el estado solamente se crea la primera vez que nuestro componente se renderiza. Durante los siguientes renderizados, useState nos da el estado actual. Esta es también la razón por la que los nombres de los Hooks siempre empiezan con use.

Les proponemos un desafío!, se anima a agregar un botón que en lugar de aumentar en 1 el estado, lo disminuya?
Pueden practicar accediendo a este link de codepen: Ejemplo de Hook Basico

Estados en React

Hasta el momento vimos que el hook useState sirve para añadir y manejar una variable de estado a un componente funcional, pero no definimos que es un estado en React.

Los estados son el “corazón” de los componentes de React. Son aquellas características que permiten desarrollar aplicaciones mucho más interesantes ya que sirven para hacer interactivos nuestros componentes.

El estado de un componente es la memoria en cada momento que tiene la instancia de un componente que se está mostrando en pantalla. Se trata de un atributo de la instancia parecido a las props, al que podremos acceder con this.state. Al contrario que las props, el estado varía durante el tiempo en que el componente aparece pintado en la pantalla.

Cada instancia de un componente tiene un estado que refleja cada uno de esos pequeños cambios. React encapsula toda la complejidad y la distribuye en este sistema predecible. Saber utilizar los estados nos permitirá definir cómo se comportarán los componentes en cada momento y declarar reacciones más elaboradas a según la interacción con el usuario.

Profundizando en Hook useState

El Hook useState es muy fácil de usar, sin embargo debemos comprender claramente su funcionamiento.
Para ello veremos los siguientes conceptos que nos van a ayudar a usar correctamente un Hook useState y a evitar los errores comunes:

  • Las actualizaciones de estado con useState no se combinan automáticamente

    Una gran ventaja del hook useState es que podemos llamarlo tantas veces como queramos para usar tantas variables de estado como necesitemos. Por ejemplo podríamos tener un formulario básico con una entrada de nombre y profesión ,y en donde gestionamos el estado del nombre y profesión como variables de estado individuales de la siguiente manera:

    const [nombre, setNombre] = useState("");
    const [profesion, setProfesion] = useState("");

    Sin embargo podríamos querer administrar nuestro estado de formulario dentro de un solo objeto. Esto nos permite llamar a useState solo una vez, donde nombre y profesión no son administrados por variables de estado individuales sino como propiedades de esta variable de estado llamada state.

    Por ejemplo si usáramos un controlador de eventos genérico que está conectado a la propiedad onChange de cada una de las entradas de nuestro formulario, se vería así:

    import React, { useState } from 'react'; 
    
    const Formulario = () => {
      const [state, setState] = useState({
        nombre: "",
        profesion: ""
      });
    
      const handleInputChange = (e) => {
        setState({
          [e.target.name]: e.target.value
        });
      };
    
      return (
        <form>
          <label for="nombre">Nombre:</label>
          <input type="text" name="nombre" onChange={handleInputChange} />
          <br />
          <label for="profesion">Profesión:</label>
          <input type="text" name="profesion" onChange={handleInputChange} />
          <h2>Se ingreso el nombre: {state.nombre} </h2>
          <h2>Se ingreso la profesión: {state.profesion} </h2>
          <button type="submit">Submit</button>
        </form>
      );
    };
    
    Ahora estamos actualizando el valor de cada entrada en estado de acuerdo con el nombre de la entrada que nuestro usuario está escribiendo actualmente. Este patrón se usa comúnmente para actualizar el estado en componentes basados en clases, pero esto no funciona con el hook useState.
    Las actualizaciones de estado con setState no se combinan automáticamente!.
    Esto significa que siempre que establecemos el estado a medida que nuestro usuario escribe, el estado anterior no se incluye en el nuevo estado, por ejemplo si escribimos el nombre y luego empezamos a escribir la profesión, el valor del estado para el nombre se pierde ya que no se combina automáticamente.
    Pueden ver el ejemplo funcionando accediendo a este link: Formulario con Estados y Objetos

    Dado que el estado anterior no se fusiona automáticamente con el nuevo objeto de estado, debemos fusionar manualmente nuestro objeto de estado con sus propiedades anteriores utilizando el operador de propagación del objeto de manera que nuestra función handleInputChange quedaría de la siguiente manera:
    const handleInputChange = (e) => {
    	setState({
          ...state, // usamos el operador spread para propagar los estados
          [e.target.name]: e.target.value
        })
    }
    
    De esta manera mantendremos el valor de los estados siempre con los valores correctos.
    Pueden ver el ejemplo funcionando accediendo a este link: Formulario con Estados y Spread

    En resumen el hook useState, nos ofrece la flexibilidad de administrar múltiples valores primitivos o usar un objeto con múltiples propiedades. Sin embargo, si usamos useState con un objeto, debemos recordar propagar en el estado anterior cuando se realice cualquier actualización para asegurarse de que se actualice correctamente.

  • Las actualizaciones de estado deben ser inmutables

    Una parte muy importante del estado en React es que debe actualizarse y administrarse de la manera correcta. Cuando se trata de administrar el estado con el hook useState, debemos usar solo la función de useState dedicado a modificar el estado y se proporciona como el segundo elemento en el arreglo que obtenemos de useState para actualizarlo.

    Si no usamos React e intentamos actualizarlo manualmente, por ejemplo, con la ayuda de JavaScript plano entonces no podemos esperar que nuestra aplicación refleje los cambios que hicimos en el estado. En otras palabras, si actualizamos el estado con JavaScript plano y no con setState, no se activará una nueva renderización y React no mostrará esos cambios (inválidos) en el estado a nuestro usuario.

    Al intentar actualizar el estado manualmente, estamos violando un principio basico de React. Este es el concepto de inmutabilidad, que nos dice que:"las actualizaciones de estado siempre deben ser inmutables".

    Esto significa que no deberíamos hacer nuestros propios cambios o mutar los datos almacenados en nuestras variables de estado. Hacerlo hace que nuestro estado sea impredecible y puede causar problemas no deseados en nuestra aplicación que son difíciles de depurar.

    import React from 'react';
    
    export default function App() {
      const [count, setCount] = React.useState(0);
      
      // No asigne estado a nuevas variables (que no usen el hook useState)
      const newCount = count;
      // No mutes directamente el estado
      const countPlusOne = count + 1;
    
      return (
        <>
          <h1>Count: {count}</h1>
        </>
      );
    }
    
    Además de no mutar las variables de estado directamente, debemos asegurarnos de no asignar nunca variables de estado a otras variables (que no usen el hook useState).

  • Las actualizaciones de estado son asincrónicas y programadas

    Un concepto importante que debemos saber sobre las actualizaciones del estado es que no se realizan de inmediato.

    Según la documentación de React cuando llamamos a la función setState la usamos para actualizar la variable de estado asociada con él, pero también se nos dice que “Acepta un nuevo valor de estado y pone en cola una reproducción del componente.”

    En otras palabras, no vuelve a renderizar el componente inmediatamente, no detiene nuestro código justo en esa línea donde actualizamos el estado, pero tiene lugar en algún momento en el futuro.

    A partir de esto ahora sabemos que cuando intentamos actualizar el estado: la función setState no actualiza inmediatamente el estado, simplemente programa una actualización de estado para algún tiempo en el futuro. Después de lo cual, React se encarga de averiguar cuándo se lleva a cabo esa actualización de estado.

  • El estado obsoleto puede ocurrir con los closures

    Un problema importante que puede ocurrir con el estado React es el problema del estado obsoleto que ocurre cada vez que intentamos actualizar el estado, a menudo dentro de un closure. El problema está relacionado con que las actualizaciones de estado son asincrónicas. En muchos casos, el problema de que las actualizaciones de estado sean asincrónicas es que no siempre obtenemos el valor anterior correcto de nuestro estado, especialmente si intentamos actualizar el estado en función de ese valor anterior.

    Nota:

    Un closure es un tipo de función en JavaScript, donde usamos una variable de un ámbito externo.
    Para profundizar sobre el concepto se recomienda ir a este link:Closures.

    A continuación veremos un ejemplo de el problema de un closure obsoleto dentro de una aplicación de contador simple que actualiza el recuento después de un segundo usando la función setTimeout.
    import { useState } from 'react';
    
    const Contador = () => {
      const [count, setCount] = useState(0);
      
      const delayAddOne = () => {
        setTimeout(() => {
          setCount(count + 1);
        }, 1000);
      }
    
      return (
        <>
          <h1>El botón ha sido pulsado {count} veces</h1>      
          <button onClick={delayAddOne}>
             Púlsame
          </button>      
        </>
      )
    };
    
    Debido a que setTimeout crea un closure, estamos accediendo a un valor obsoleto de nuestra variable de estado, count, cuando llamamos a setCount. Por ejemplo si presionamos rápidamente 3 veces consecutivas el boton solo se mostrara que el estado es 1 ya que por el delay no se llego a actualizar el estado correspondiente a los otros “clicks”.
    Pueden ver el ejemplo accediendo a este link: Contador con Estado Obsoleto

    Podemos solucionar este problema del estado obsoleto dentro de nuestro closure utilizando un método más confiable como proporcionar una función interna a la función setState. En el cuerpo de la función, podemos obtener el estado anterior dentro de los parámetros de esta función y luego devolver lo que queremos que sea el siguiente estado que en nuestro caso, será el valor de recuento anterior incrementado en uno.

    Las actualizaciones de estado todavía se van a programar, pero permitirán obtener de forma fiable el valor de estado anterior.

    Por lo tanto la función para actualizar correctamente el estado y no caer el el problema del estado obsoleto quedaria de la siguiente forma:
    const delayAddOne() {
        setTimeout(() => {
          // El problema del estado obsoleto desaparece usando una función interna
          setCount(prevCount => prevCount + 1);
        }, 1000);
    }
    
    Para ver el ejemplo funcionando puede acceder al siguiente link de codepen: Resolviendo Estado Obsoleto

Mandanos tus sugerencias

Ayudanos con ideas para los artículos de este blog a contacto@somospnt.com

¡Seguínos en nuestras redes sociales para enterarte de los últimos posts!