Cambiar el estado del componente padre en un componente hijo en React

En la lección anterior, el estado con los datos se almacenaba en el componente padre, y los componentes hijos recibían estos datos a través de props.

Ahora supongamos que queremos modificar nuestros productos. Hagamos, por ejemplo, un botón que agregue nuestro producto al carrito. Para empezar, agreguemos a nuestro array de productos el campo inCart, que indica si el producto está en el carrito o no:

jsxconst initProds = [
  {id: id(), name: 'product1', cost: 100, inCart: false},
  {id: id(), name: 'product2', cost: 200, inCart: false},
  {id: id(), name: 'product3', cost: 300, inCart: false},
];

En el componente Products, en la etiqueta del producto agreguemos otro atributo inCart:

jsxfunction Products() {
  const [prods, setProds] = useState(initProds);
  
  const items = prods.map(prod => {
    return <Product
      key   ={prod.id}
      name  ={prod.name}
      cost  ={prod.cost}
      inCart={prod.inCart}
    />;
  });
  
  return <div>
    {items}
  </div>;
}

Hagamos en el componente hijo Product la salida de información sobre el carrito y un botón para agregar al carrito:

jsxfunction Product({ id, name, cost, inCart }) {
  return <div>
    name: <span>{name}</span>,
    cost: <span>{cost}</span>,
    <span>{inCart ? 'en el carrito' : 'no en el carrito'}</span>
    <button>al carrito</button>
  </div>;
}

Implementemos la adición

Según las reglas de React, un componente no debe cambiar sus props. Esto significa que el componente hijo no puede agregarse a sí mismo al carrito cambiando la prop inCart. Esto no es correcto.

Lo correcto es pedir al componente padre que cambie su estado prods, colocando un producto determinado en el carrito.

Veamos cómo se hace.

En el componente padre, hagamos una función addToCart, que acepta como parámetro el id del producto y para ese producto cambia la propiedad inCart a true:

jsxfunction addToCart(id) {
  setProds(prods.map(prod => {
    if (prod.id === id) {
      prod.inCart = true;
    }
    
    return prod;
  }));
}

En la etiqueta del producto agreguemos un atributo, al cual le pasaremos la función que creamos, así como un atributo, al cual le pasaremos el id del producto:

jsxconst items = prods.map(prod => {
  return <Product
    key      ={prod.id}
    id       ={prod.id}
    name     ={prod.name}
    cost     ={prod.cost}
    inCart   ={prod.inCart}
    addToCart={addToCart}
  />;
});

Como puedes ver, en las props de los componentes se pueden pasar no solo datos, sino también funciones.

El código final de la función Products quedará así:

jsxfunction Products() {
  const [prods, setProds] = useState(initProds);
  
  function addToCart(id) {
    setProds(prods.map(prod => {
      if (prod.id === id) {
        prod.inCart = true;
      }
      
      return prod;
    }));
  }
  
  const items = prods.map(prod => {
    return <Product
      key      ={prod.id}
      id       ={prod.id}
      name     ={prod.name}
      cost     ={prod.cost}
      inCart   ={prod.inCart}
      addToCart={addToCart}
    />;
  });
  
  return <div>
    {items}
  </div>;
}

Ahora en la función hija tendremos disponible la función addToCart. Llamemos a esta función al hacer clic en el botón, pasándole como parámetro el id del producto:

jsxfunction Product({ id, name, cost, inCart, addToCart }) {
  return <div>
    name: <span>{name}</span>,
    cost: <span>{cost}</span>,
    <span>{inCart ? 'en el carrito' : 'no en el carrito'}</span>
    <button onClick={() => addToCart(id)}>al carrito</button>
  </div>;
}

El resultado será que al hacer clic en el botón en el componente hijo se llamará a la función del padre, que cambiará el estado del padre. El cambio del estado del padre provocará un rerenderizado y redibujará nuestro producto, pasándole la prop modificada.

Задание 1

Tome el componente User de la lección anterior. Haga que aparezca un botón para banear al usuario.