Ändra föräldrakomponentens state i en barnkomponent i React

I föregående lektion hade vi state med data lagrat i föräldrakomponenten, medan barnkomponenterna fick dessa data som props.

Låt oss nu säga att vi vill ändra våra produkter. Låt oss göra en knapp till exempel som kommer att lägga vår produkt i varukorgen. Till att börja med låt oss lägga till ett fält inCart i vår array med produkter som visar om produkten är i varukorgen eller inte:

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

I komponenten Products lägger vi till ytterligare ett attribut inCart i taggen med produkten:

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

Låt oss i barnkomponenten Product göra en utskrift av information om varukorgen och en knapp för att lägga till i varukorgen:

jsxfunction Product({ id, name, cost, inCart }) {
  return <div>
    name: <span>{name}</span>,
    cost: <span>{cost}</span>,
    <span>{inCart ? 'in cart' : 'not in cart'}</span>
    <button>to cart</button>
  </div>;
}

Implementera tillägget

Enligt Reacts regler får en komponent inte ändra sina props. Det betyder att barnkomponenten inte kan lägga sig själv i varukorgen genom att ändra prop inCart. Det är inte korrekt.

Korrekt är att be föräldrakomponenten ändra sitt state prods genom att lägga en specifik produkt i varukorgen.

Låt oss titta på hur detta görs.

I föräldrakomponenten skapar vi en funktion addToCart, som tar ett id för produkten som parameter och för den produkten ändrar egenskapen inCart till true:

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

I taggen med produkten lägger vi till ett attribut, där vi skickar funktionen vi skapat, samt ett attribut, där vi skickar produktens id:

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

Som ni ser kan man i komponenters props skicka inte bara data, utan även funktioner.

Den slutgiltiga koden för klassen Products kommer att bli följande:

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

Nu i barnklassen kommer vi att ha tillgång till funktionen addToCart. Låt oss anropa denna funktion vid klick på knappen, och skicka med produktens id som parameter:

jsxfunction Product({ id, name, cost, inCart, addToCart }) {
  return <div>
    name: <span>{name}</span>,
    cost: <span>{cost}</span>,
    <span>{inCart ? 'in cart' : 'not in cart'}</span>
    <button onClick={() => addToCart(id)}>to cart</button>
  </div>;
}

Det kommer att resultera i att vid klick på knappen i barnkomponenten anropas förälderns funktion, som kommer att ändra förälderns state. Förändringen av förälderns state kommer att orsaka en omrendering och kommer att rita om vår produkt, och skicka med den ändrade proppen.

Задание 1

Ta komponenten User från föregående lektion. Gör så att en knapp för att banna användaren dyker upp i den.