Jerarquía entre Componentes: pasando estados y otros datos através de props

Jerarquía entre Componentes: pasando estados y otros datos através de props

React nos permite pasar variables y funciones de estado de un componente a otro a través de props.

Introducción

Como hemos visto anteriormente en anteriores artículos como este y este, nuestra variable de estado no solo recibe valores como también podemos pasarle estructuras de datos como arrays y objetos.

React nos permite pasar estas variables de estado entre componentes através de props.

Recuerda: El estado son los datos/valores dentro de nuestro Componente funcional que queramos que cambien, se mantenga un seguimiento de ellos , y se vuelvan a renderizar.

Contexto

Para explicar esta transferencia de estado entre Componentes, voy a usar como base la tarjeta de contacto que usé en este post.:

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>
    )
}

Pasando estado a través de props.

Para fines de este artículo, voy a separar la imagen de la estrella en su propio Componente.

Para que quede un poco más claro, dejo la estructura del proyecto.

estructura contador.png

  • index.html: contiene nuestro HTML con el div "root"
  • index.js: importa el Componente App y lo renderiza en "root"
  • App.js: Contiene nuestra tarjeta de contacto, asi como el estado y manejador de eventos, importa el Componente Star y le pasa el estado y el manejador de eventos como props.
  • Star.js: Recibe el estado y el manejador de eventos para poder renderizar la estrellita de favorito.

App.js

import React from "react"
import Star from "./Star";

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


    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">
                    <Star isFilled={contacto.esFavorito} handleClick= {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 podemos pasar el estado como si se tratara de una variable tradicional a través de props y luego recibirla y utilizarla en otro Componente. En este caso también paso la función toggleFavorito.

Podemos pensar en un principio en pasar directamente onClick = {toggleFavorito} como propiedad a nuestro Componente Star, pero cuando creamos un Componente, todas las propiedades que le pasemos dentro de su etiqueta son propiedades personalizadas.

Star.js

import React from "react";

function Star(props) {

const starIcon = props.isFilled ? "star-filled.png" : "star-empty.png";

    return (
        <img
            src={`./images/${starIcon}`}
            className="card--favorite"
            onClick={props.handleClick}
        />
    )
}

export default Star;

Al recibir las propiedades en nuestro Componente "hijo", debemos referenciarlas usando el objeto que se recibe como parametro. (props)

toggleFavorito es pasada como toggleFav, y es un manejador de eventos, el cual usar una función de estado para modificar una propiedad en concreto del estado.

Al ser un elemento HTML, a <img> image sí que le podemo pasar onClick y luego referenciar a nuestra función toggleFavorito (que está declarada dentro de App) a través de props.toggleFav.

Esta característica de pasar funciones de estado a un Componente hijo es crucial en React. Esto se debe a la manera en que la hierarquía en React es establecida cuando pasamos datos.

Jerarquía entre Componentes

Imaginemos que tenemos una aplicación simple que dispone de estos componentes (representados como rectángulos).

En este árbol el Componente raíz representa un Componente de más alto nivel en la jerarquía.

Las lineas representan Componentes que renderizan otros Componentes.

passing-data1.png

En algunos casos estos Componentes renderizan elementos HTML , en otros renderizan otros Componentes. Como podemos observar hay una relación padre-hijo entre estos Componentes.

Ahora imaginemos que se declara un estado en un Componente hijo (A), y que un Componente "hermano"(B) (un componente que es renderizado por el mismo padre) necesita acceder a ese estado. No hay manera de pasar ese estado directamente al "hermano", dado que no hay ninguna referencia en el Component B hacia el Componente A.

passing-data2.png

Si nos encontramos con una situación como esta, debemos trasladar el estado del Componente A a su padre, por lo que éste se hace cargo del estado y los suministra a los Componentes hijos que lo requieran a través de props.

Si el estado alguna vez cambia, React actualizaría el Componente padre y éste en consecuencia, actualizaría sus hijos.

passing-data3.png

Al volverse cada vez más compleja, una App tiende a tener más Componente que requieran el mismo estado, por lo que debemos tener en cuenta que no se puede pasar datos "hacia arriba", es decir de un hijo a su padre o a su "tío". Además podemos vernos en un escenario donde nuestro estado sube varios niveles acima del Componente que lo requiere, en dicho caso React ofrece "React Context" (Context provee una forma de pasar datos a través del árbol de componentes sin tener que pasar props manualmente en cada nivel), y también podemos usar Redux para gestionar estados.

Entender la forma en la que los datos fluyen en React es crucial para la arquitectura para que compartamos el estado solamente a los Componentes que lo necesiten