Alterando o estado do componente pai em um componente filho no React

Na lição anterior, o estado com os dados era armazenado no componente pai, e os componentes filhos recebiam esses dados via props.

Agora, suponha que queiramos alterar nossos produtos. Vamos criar, por exemplo, um botão que irá adicionar nosso produto ao carrinho. Para começar, vamos adicionar ao nosso array de produtos um campo inCart, indicando se o produto está no carrinho ou não:

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},
];

No componente Products, na tag do produto, vamos adicionar mais um 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>;
}

Vamos no componente filho Product fazer a exibição da informação sobre o carrinho e um botão para adicionar ao carrinho:

jsxfunction Product({ id, name, cost, inCart }) {
  return <div>
    name: <span>{name}</span>,
    cost: <span>{cost}</span>,
    <span>{inCart ? 'no carrinho' : 'não está no carrinho'}</span>
    <button>para o carrinho</button>
  </div>;
}

Vamos implementar a adição

De acordo com as regras do React, um componente não deve alterar suas próprias props. Isso significa que um componente filho não pode se adicionar ao carrinho alterando a prop inCart. Isso não está correto.

A maneira correta é pedir ao componente pai que altere seu estado prods, colocando um determinado produto no carrinho.

Vamos ver como isso é feito.

No componente pai, vamos criar uma função addToCart, que recebe como parâmetro o id do produto e para esse produto altera a propriedade inCart para true:

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

Na tag do produto, vamos adicionar um atributo no qual passaremos a função que criamos, e também um atributo no qual passaremos o id do produto:

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 você pode ver, nas props dos componentes é possível passar não apenas alguns dados, mas também funções.

O código final do componente Products ficará da seguinte forma:

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

Agora, no componente filho, teremos acesso à função addToCart. Vamos chamar essa função no clique do botão, passando para ela como parâmetro o id do produto:

jsxfunction Product({ id, name, cost, inCart, addToCart }) {
  return <div>
    name: <span>{name}</span>,
    cost: <span>{cost}</span>,
    <span>{inCart ? 'no carrinho' : 'não está no carrinho'}</span>
    <button onClick={() => addToCart(id)}>para o carrinho</button>
  </div>;
}

O resultado é que, ao clicar no botão no componente filho, a função do pai será chamada, e esta irá alterar o estado do pai. A alteração do estado do pai irá acionar uma nova renderização e redesenhará nosso produto, passando para ele a prop alterada.

Задание 1

Pegue o componente User da lição anterior. Faça com que apareça nele um botão para banir o usuário.