Промяна на състоянието на родителя в детски компонент в React

В предишния урок нашето състояние с данни беше съхранено в родителския компонент, а детските компоненти получаваха тези данни под формата на пропсове.

Нека сега искаме да променяме нашите продукти. Нека направим, например, бутон, който ще поставя нашия продукт в количката. Като начало нека добавим в нашия масив с продукти поле inCart, показващо дали продуктът е в количката или не:

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

В компонента Products в тага с продукта добавяме още един атрибут 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>;
}

Нека в детския компонент Product направим извеждане на информация за количката и бутон за добавяне в количката:

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

Реализираме добавянето

По правилата на React компонентът не трябва да променя своите пропсове. Това означава, че детският компонент не може да се сложи сам в количката, като промени пропса inCart. Това не е правилно.

Правилно ще бъде да помолим родителския компонент да промени своето състояние prods, като сложи определен продукт в количката.

Нека да видим как се прави това.

В компонента-родител направим функция addToCart, която параметър приема id на продукта и за този продукт променя свойството inCart на true:

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

В тага с продукта добавяме атрибут, в който предаваме създадената от нас функция, както и атрибут, в който предаваме 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}
  />;
});

Както виждате, в пропсовете на компонентите може да се предават не само някакви данни, но и функции.

Крайният код на класа Products ще се получи следният:

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

Сега в детския клас ще имаме достъпна функцията addToCart. Извикваме тази функция при кликване на бутона, като й подаваме параметър id на продукта:

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

Получава се, че при кликване на бутона в потомъка ще се извика функция на родителя, която ще промени родителското състояние. Промяната на родителското състояние ще предизвика пререндериране и ще прерисува нашия продукт, като му предаде променения пропс.

Задание 1

Вземете компонента User от предишния урок. Направете така, че в него да се появи бутон за бан на потребителя.