การเปลี่ยน state ของ parent ใน child component ใน React

ในบทเรียนก่อนหน้านี้ state ที่เก็บข้อมูลของเรา ถูกเก็บไว้ใน component หลัก (parent) ในขณะที่ component ย่อย (child) ได้รับข้อมูลเหล่านั้นผ่าน props

สมมติว่าตอนนี้เราต้องการเปลี่ยนแปลงข้อมูลสินค้าของเรา เรามาสร้างปุ่ม เช่น ปุ่มที่จะ นำสินค้าของเราใส่ลงในตะกร้าสินค้า ก่อนอื่น เรามาเพิ่มฟิลด์ 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},
];

ใน component 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>;
}

มาแสดงข้อมูลเกี่ยวกับตะกร้าและปุ่ม สำหรับเพิ่มลงในตะกร้าใน component ย่อย 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 component ไม่ควร เปลี่ยนแปลง props ของตัวเอง นั่นหมายความว่า component ย่อยไม่สามารถเพิ่มตัวเอง ลงในตะกร้าได้โดยการเปลี่ยน prop inCart นั่นไม่ถูกต้อง

วิธีที่ถูกต้องคือขอให้ component หลัก เปลี่ยน state prods ของตัวเอง โดยเพิ่ม สินค้าที่กำหนดลงในตะกร้า

มาดูกันว่าทำได้อย่างไร

ใน component หลัก สร้างฟังก์ชัน 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}
  />;
});

อย่างที่คุณเห็น ใน props ของ component สามารถ ส่งผ่านได้ไม่เพียงแค่ข้อมูลบางอย่าง แต่ รวมถึงฟังก์ชันด้วย

โค้ดสุดท้ายของคลาส 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>;
}

ผลลัพธ์คือ เมื่อคลิกปุ่มใน component ลูก จะเรียกใช้ฟังก์ชันของ component หลัก ซึ่งจะเปลี่ยน state ของ component หลัก การเปลี่ยน state ของ component หลัก จะทำให้เกิดการเรนเดอร์ใหม่และวาด สินค้าของเราใหม่ โดยส่ง prop ที่เปลี่ยนแปลงแล้วให้กับมัน

Задание 1

นำ component User จากบทเรียนก่อนหน้า มาทำให้มีปุ่มสำหรับแบนผู้ใช้