Banner keyboard

Andrés D'Amelio

Desarrollador web Front-end

Título

Ingeniero en informática

Ubicación

Venezuela

React Hooks: Guía useState

Publicado el 29 de marzo de 2022

A partir de la versión 16.8 se incorporan los Hooks en React con el propósito de manejar el estado y ciclo de vida en los componentes de función. Los Hooks son funciones que nos permiten usar React sin clases, estas funciones podemos importarlas desde el paquete React. Tenemos muchos Hooks disponibles para usar, uno de ellos es useState quizás uno de los más importantes y usados en React.


Inicialización del estado

El Hook useState nos permite manejar el estado en un componente de función, este estado funciona como una memoria que almacena los datos de un componente y conserva estos datos en el ciclo de vida del componente. Veamos un ejemplo de como usarlo:

import { useState } from 'React';

const MyComponent = () => {
    const [count, setCount] = useState(0);
};

export default MyComponent;

Lo primero que hacemos es importarlo desde React, luego de esto tenemos a nuestra disposición la función para crear las variables de estado que necesitemos en nuestro componente.


useState toma un valor inicial, este valor puede ser de cualquier tipo de dato disponible en Javascript, en el ejemplo anterior utilizamos un valor numérico, pero puede ser una cadena de texto, un objeto, un arreglo, un valor booleano, etc.


Este Hook retorna un arreglo, donde el primer valor es el estado actual, y la segunda es una función que usaremos para actualizar el valor de nuestro estado. Para obtener los elementos del arreglo retornado utilizamos la desestructuración de arreglos. En el ejemplo anterior observamos que tenemos como primer elemento count que representa nuestra variable de estado, su valor inicial para este caso es 0, y como segundo elemento setCount que representa la función para actualizar el valor de la variable count.


Nuestra variable de estado estará disponible en nuestro componente y la podemos usar de forma directa

import { useState } from 'React';

const MyComponent = () => {
    const [count, setCount] = useState(0);

    return <h1>Contador { count }<h1>
};

export default MyComponent;

Con esto veremos en pantalla el texto "Contador 0".


Actualización del estado

Para cambiar nuestra variable de estado haremos uso del segundo elemento retornado por useState, esta función la podemos utilizar de diversas formas, veamos cada una de ellas utilizando el ejemplo anterior.

import { useState } from 'React';

const MyComponent = () => {
    const [count, setCount] = useState(0);

    return (
        <>
            <h1>Contador { count }</h1>
            <button onClick={() => setCount(count - 1)}>Decrementar</button>
            <button onClick={() => setCount(count + 1)}>Incrementar</button>
        <>
    );
};

export default MyComponent;

En este ejemplo utilizamos la función setCount en el evento click de los botones, por lo que al dar click a cualquiera de los botones se actualizará la variable count. Es importante resaltar qué para usar la función de actualización de esta forma debemos de pasar al evento click una función que retorne el setCount, con esto aseguramos que la función se ejecute solo al darle click. Si lo hiciéramos así onClick={ setCount(count - 1)} ó onClick={setCount(count + 1)} tendríamos un error, porque se estaría ejecutando la función múltiples veces lo que ocasionaría muchísimos renderizados, y React limita el número de llamadas a la función render para evitar ciclos infinitos.


Si no tenemos acceso directo a la variable de estado, dentro de la función de actualización podemos obtener el valor directamente, y actualizarlo, veamos:

import { useState } from 'React';

const MyComponent = () => {
    const [count, setCount] = useState(0);

    return (
        <>
            <h1>Contador { count }</h1>
            <button onClick={() => setCount((prev) => prev - 1)}>Decrementar</button>
            <button onClick={() => setCount((prev) => prev + 1)}>Incrementar</button>
        <>
    );
};

export default MyComponent;

Al utilizar la función de actualización, el estado previo se actualiza, y toma un nuevo valor. Cuando nuestro estado es un arreglo u objeto debemos tener precaución porque podemos perder el valor del estado previo, ya que estos no se fusionan, veamos un ejemplo:

const [user, setUser] = useState({
    name: 'Andrés',
    age: 25
});

setUser({ name: 'José' });

En esta caso podríamos pensar que el nuevo valor de la variable de estado user es

{ name: 'José', age: 25 }

Pero no es el caso, al invocar setUser estamos asignando un nuevo valor, por lo que el valor anterior se pierde, y así el nuevo valor es

{ name: 'José' }

Para evitar esto, y conservar la estructura de nuestro estado, debemos realizar una fusión de forma manual, es decir, actualizar únicamente el valor sin perder la forma inicial, para esto usamos el estado anterior junto con el operador de propagación, veamos:

const [user, setUser] = useState({
    name: 'Andrés',
    age: 25
});

setUser({ ...user, name: 'José' });

Con esto mantenemos la estructura de nuestro estado inicial, y solo actualizamos el valor de la propiedad name mediante el operador de propagación.


useState tiene el mismo comportamiento con los arreglos, debemos fusionarlos de forma manual, veamos un ejemplo:

const [colours, setColours] = useState(['red', 'blue', 'yellow']);

setColours([ ...colours, 'white' ]);

Nota: Cuando trabajamos con arreglos u objetos anidados se recomienda separarlos en varias variables de estado porque esto resulta costoso para el rendimiento de nuestras aplicaciones, pues React puede volver a renderizar elementos donde usemos propiedades que no se han actualizado.


El Hook useState también nos permite trabajar con formularios de forma rápida y sencilla, veamos como:

 import { useState } from 'React';

const MyComponent = () => {
    const [user, setUser] = useState({
        name: '',
        age: 0
    });

    const handleChange = (name) => (event) => {
     setUser({ ...user, [name]: event.target.value });
    };

    return (
        <form>
          <input
            placeholder="Nombre"
            value={user.name}
            onChange={handleChange('name')}
          />
          <input
            type="number"
            placeholder="Edad"
            value={user.age}
            onChange={handleChange('age')}
          />
          <button type="submit">Crear</button>
        </form>
    );
};

export default MyComponent;

Llamadas múltiples a useState

En algunos casos puede ocurrir que nuestra función de actualización sea llamada varias veces de forma simultánea, esto puede ser un problema al momento de actualizar nuestro estado dependiendo de la forma en la que se está invocando a la función de actualización. Veamos un pequeño ejemplo de un formulario de datos de usuario donde además tendremos un componente de imagen que realiza una operación asíncrona en el servidor y devuelve la URL de una imagen.


import { useState } from 'React';
import Upload from './components/Upload';

const MyComponent = () => {
    const [user, setUser] = useState({
        name: '',
        age: 0,
        photo: ''
    });

    const handleChange = (name) => (event) => {
      setUser({ ...user, [name]: event.target.value });
    };

     const handleAddPhoto = (url) => {
      setUser({ ...user, photo: url });
     };

    return (
        <form>
            <Upload
              value={user.photo}
              setImage={handleAddPhoto}
            />
            <input
              placeholder="Nombre"
              value={user.name}
              onChange={handleChange('name')}
            />
            <input
              type="number"
              placeholder="Edad"
              value={user.age}
              onChange={handleChange('age')}
             />
             <button type="submit">Crear</button>
        </form>
    );
};

export default MyComponent;

Si por ejemplo, seleccionamos una imagen para ser subida y llenamos el formulario, al terminarse la operación de subida y ejecutarse la función handleAddPhoto veremos que los cambios que hicimos en nuestro formulario luego de adjuntar la foto se perderán. ¿A qué se debe esto? En este caso las funciones handleChange y handleAddPhoto tienen un cierre (closure) de user y su valor asignado en el renderizado actual. Por lo que en este caso al renderizarse el componente para ambas funciones user tiene el mismo valor, así que al invocarse se estará actualizando con el valor inicial. Ahora en nuestro ejemplo si subimos nuestra imagen se realiza una petición hacía algún servidor, esta operación no se realiza de forma inmediata por lo que al terminar actualizará el estado, y al tener el cierre inicial, cancelará todos los cambios que se hicieron en el formulario luego de subir la imagen.

Una solución para este problema es utilizar en nuestra función de actualización un callback para obtener el estado actualizado y evitar este problema, nuestra funciones quedaría de la siguiente forma


const handleChange = (name) => (event) => {
    setUser((previousState) => { ...previousState, [name]: event.target.value });
};

const handleAddPhoto = (url) => {
    setUser((previousState) => { ...previousState, photo: url });
};

Con esto nuestro estado se irá actualizando tomando como referencia el estado actual.


Conclusión

useState es uno de los Hooks más importantes y usados que debes conocer, por el papel fundamental que tienen en el manejo del estado en nuestros componentes, ya sabes cómo usarlo y puedes ponerlo en práctica en tus proyectos. Si te gustó compártelo en tus redes y así ayudamos a más personas a entender este concepto tan confuso en javascript. Si tienes alguna duda déjala en los comentarios y te responderé en la brevedad posible.

¿Sobre que otro hook te gustaría ver un artículo?