useState: Usando arrays y objetos como estado.

useState: Usando arrays y objetos como estado.

El estado puede no solo almacenar valores como también estructura de datos como arrays y objetos.

Introducción

En esta jornada para entender los estados en React, voy a continuar con el ejemplo que di en mi primer artículo de React acerca de props vs estado para primero explicar array como estado.

En dicho ejemplo "uso un componente donde disponemos de un array de strings e iteramos sobre dicho array y generamos un párrafo para cada uno de sus elementos y los renderizamos en pantalla".

Al hacer clic en el botón, se añaden nuevos elementos al array pero la página no se actualiza, por lo que vamos a dotarla de un estado. Usando los conocimientos aprendidos en mi artículo sobre useState.

react state exercises 2.png Los requisitos para entender este artículo son conocimientos sobre:

Tengo un artículo sobre cada uno de esos requisitos en los enlaces.

Array como estado

En lugar de asignar un valor a nuestra variable de estado, podemos asignarle estructura de datos como pueden ser arrays y objetos.

La aplicación la habiamos dejado de este modo:

import React from 'react';
import ReactDOM from 'react-dom';


function App() {
    const arrayDeCosas = ["Elemento 1", "Elemento 2"]
    const elementosP = thingsArray.map(thing => <p key={thing}>{thing}</p>)

    const handleButton = () => {
        arrayDeCosas.push(`Elemento ${thingsArray.length + 1}`);
        console.log(arrayDeCosas);
    }

    return (
        <div>
            <button onClick = {handleButton }>Add Item</button>
            {arrayDeCosas}
        </div>
    )
}

ReactDOM.render(<App />, document.getElementById('root'));

Podríamos pensar en un principio en usar arrayDeCosas.push() para añadir un nuevo elemento a nuestro array, pero si recordamos el artículo sobre estados, nunca se debe modificar el valor de la variable de estado directamente, y el método push() es un método destructivo, además de que añade un nuevo elemento pero retorna la nueva longitud del array, por lo que (si React lo permitiera) estaríamos sobrescribiendo la variable de estado con un número.

import React from 'react';
import ReactDOM from 'react-dom';

function App() {

    const [arrayDeCosas,setCosas] = React.useState(["Elemento 1","Elemento 2"]);

    function addItem() {
        setCosas((array) => {
            return [...array,`Elemento ${array.length + 1}`];
        })
    }

    const elementos = arrayDeCosas.map(cosa=> <p key={cosa}>{cosa}</p>)

    return (
        <div>
            <button onClick={addItem}>Add Item</button>
            {elementos}
        </div>
    )
}

ReactDOM.render(<App />, document.getElementById('root'));

Como podemos observar, debemos pasarle una función callback a la función de estado, a la cual recibe el estado anterior (nuestro array) como argumento, pero retorna un nuevo array en el que usaremos el operador spread para conservar los elementos del array, además de añadir uno nuevo.

spread array.png

Objeto como estado

Para explicar objetos como estado usaré un Componente donde se renderiza una tarjeta de usuario con su nombre, teléfono y correo, así como un icono de favorito (lo usaremos para explicar cómo actualizar valores de propiedades de un objeto), toda la información viene de un objeto.

jhon doe.png

El código inicial es:

import React from "react"

export default function App() {
    const [contacto, setContacto] = React.useState({
        nombre: "Juan",
        apellidos: "Doe",
        telefono: "+34 584 687 000",
        email: "minombre@example.com",
        esFavorito: true
    })

    let iconoEstrella = contacto.esFavorito ? "star-filled.png" : "star-empty.png"

    return (
        <main>
            <article className="card">
                <img src="./images/user.png" className="card--image" />
                <div className="card--info">
                    <img 
                        src={`../images/${iconoEstrella}`} 
                        className="card--favorite"
                    />
                    <h2 className="card--name">
                        {`${contacto.nombre} ${contacto.apellidos}`}
                    </h2>
                    <p className="card--contact">{contacto.telefono}</p>
                    <p className="card--contact">{contacto.email}</p>
                </div>

            </article>
        </main>
    )
}

Como podemos observar, también podemos configurar la variable de estado como siendo un objeto. A la hora de renderizar sus propiedades, lo haríamos de la manera tradicional, accediendo a objeto.propiedad, con la diferencia es que ahora podremos cambiar sus propiedades y estas se volverán a renderizar en pantalla.

Además,usamos el operator ternario (condicion ? expresion1 : expresion2) para establecer qué tipo de icono se debe renderizar en la tarjeta del contacto.

Dado que estamos usando el operador ternario para saber qué icono se debe renderizar con base en la propiedad esFavorito de nuestro estado contacto que es un objeto, vamos a implementar un manejador de eventos para que cada vez que hagamos clic en el icono, se cambie el valor de esFavorito y como consecuencia, su imagen.

import React,{useState} from "react"

export default function App() {
    const [contacto, setContacto] = useState({
        nombre: "Juan",
        apellidos: "Doe",
        telefono: "+34 584 687 000",
        email: "minombre@example.com",
        esFavorito: true
    })

    let iconoEstrella= contacto.esFavorito ? "star-filled.png" : "star-empty.png"

    function toggleFavorito() {
        setContacto((estadoAnterior) => {
            return {...estadoAnterior, esFavorito : !estadoAnterior.esFavorito}
        })
    }


    return (
        <main>
            <article className="card">
                <img src="./images/user.png" className="card--image" />
                <div className="card--info">
                    <img 
                        src={`../images/${iconoEstrella}`} 
                        className="card--favorite"
                        onClick={toggleFavorito}
                    />
                    <h2 className="card--name">
                        {`${contacto.nombre} ${contacto.apellidos}`}
                    </h2>
                    <p className="card--contact">{contacto.telefono}</p>
                    <p className="card--contact">{contacto.email}</p>
                </div>

            </article>
        </main>
    )
}

Como podemos ver, se hace de manera similar a usar un array como estado; al usar la función de estado,se retorna un nuevo objeto en el que se vuelcan las propiedades del objeto anterior (usando el operador spread ...) y, en seguida se reasigna el valor de esFavorito con su negación.