Home > Blockchain >  How do I access a child components state in React.js
How do I access a child components state in React.js

Time:06-02

Hello I am working on a shopping cart project, where user can add/subtract quantity depending on the button clicked . The problem I am having is if I move state up to the parent component the itemQty is the same for all entities. So I moved it down to child component and that keeps the states separate for each product. This works up until I need to access that itemQty to calculate the subtotal.

  • I have tried passing a function from parent to child to return the itemQty but it ended up returning itemQty for each product.
  • I have tried useRef, but I don't think it is ment for this situation and I ended up with some error.

Any help will be very appreciated.

Parent Component

export default function cart() {

  let total = useContext(getTotalContext)
  let setTotal = useContext(setTotalContext)
  const [products, setProducts] = useState([])
  const [sum, setSum] = useState(0)
  /*   const prices = products.map((x) => x.price).reduce((a, b) => a   b, 0) // use to calculate total price */
  const prices = products.map((x) => x.price).reduce((a, b) => a   b, 0)

  const itemQtyRef = useRef(null)

  useEffect(() => {
    // localStorage.clear();
    setProducts(JSON.parse(localStorage.getItem("products"))) // get add to cart data initially to create cart
    localStorage.getItem('count') === 0 ? setTotal(JSON.parse(localStorage.getItem("products")).length) : setTotal(localStorage.getItem('count')) // return length if no count total
  }, [])

  useEffect(() => {
    localStorage.setItem('count', total) // stores total for navbar after every change in dropdown etc
  }, [total])


  useEffect(() => {  // upload changes to products to storage every change
    localStorage.setItem("products", JSON.stringify(products))
    setSum(prices) // 
    console.log(products)
  }, [products])
  return (
    <div className="Page-Container">
      <div className="Product-Container">
        {products.map((product, i) => {

          return (
            <div key={i}>
              <Image
                className=""
                alt="Image Unavailable"
                src={product.image}
                width={300}
                height={300} />
              <h4 className="text-sm text-gray-700">{product.title}</h4>
              <h5 className="text-lg font-medium ">${product.price}</h5>
              <h6 className="no-underline hover:no-underline">{product.rate}/5 of {product.count} Reviews</h6> {/*Add stars to */}
              <button className="bg-black-500 hover:bg-gray-400 text-black font-bold py-2 px-4 rounded-full"
                onClick={() => { //remove product on click of x
                  setProducts(products.filter((x) => x.id !== product.id))//filters out by product id clicked
                  setTotal(total - 1)
                  //     setTotal(total-product.itemQty) // removed item qty from total
                }}>x</button>
              <QtyButton product={product} setTotal={setTotal} total={total} />
      
            </div>
          )
        })}

Child Component

export default function QtyButton(props) {
    const [itemQty, setItemQty] = useState(1)
    
    return (
        <div>
            <button className="bg-green-500 hover:bg-gray-400 font-bold py-2 px-4"
                onClick={() => {
                    setItemQty(itemQty   1)

                }}> </button>
            <button className="bg-red-500 hover:bg-gray-400 font-bold py-2 px-4" onClick={() => {
                if (itemQty > 0) {
                    setItemQty(itemQty - 1)
                }

            }}>-</button><div>Quantity: {itemQty}</div>
            
        </div>
    )
}

CodePudding user response:

Lifting the state to the parent component is the right choice here.

But you seem to have gotten a bit of tunnel vision with the state. You should instead send in functions to modify parent state and have another state object which hold "purchase product data", indexed on product.id

// at the top
const [purchases, setPurchases] = useState({})

// in the state initializing useEffect
const _products = JSON.parse(localStorage.getItem("products"))
setProducts(_products)
setPurchases(_products.reduce((acc,p) => ({ ...acc, [p.id]: 
{quantity:0}}),{}))

// in returning render
    <QtyButton onPlus={() => setPurchases(purchases => {...purchases, purchases[product.id]: { quantity: purchases[product.id]  }})}
// onMinus={}
 />

CodePudding user response:

first of all, it's not the good way.means calling a child function from parent .Normally what we do is passing the function to the child component from parent.means you can define the function in parent and pass it to child

but if you must need like this, please use ref. wrap up the child function(which u need to call from parent) with forwardRef and useImperativeHandle

for example

const QtyButton = forwardRef((props, ref) => {

const [itemQty, setItemQty] = useState(1)

   useImperativeHandle(ref, () => ({

    handleqty(qty) {
      setItemQty(itemQty)
    }

  }));
   
  return (
    <div>
      <button className="bg-green-500 hover:bg-gray-400 font-bold py-2 px-4"
          onClick={() => {
                setItemQty(itemQty   1)

            }}> </button>
      <button className="bg-red-500 hover:bg-gray-400 font-bold py-2 px-4" onClick={() => {
            if (itemQty > 0) {
                setItemQty(itemQty - 1)
             }

        }}>-</button><div>Quantity: {itemQty}</div>
        
        </div>
       )
   });

in parent

 const qtyButtonRef = useRef();
 <QtyButton ref={qtyButtonRef} product={product} setTotal={setTotal} total={total} />

and call the child function by using

qtyButtonRef.current.handleqty();
  • Related