Home > Net >  Updating state inside child and using updated value in parent
Updating state inside child and using updated value in parent

Time:10-05

I am trying to create a really basic header mini cart with cart item counter in NextJS. Im using the form state value in the header component and then using that value in the child components of the header where the numerical value is rendered.

Currently I have the total item count show correctly when I submit my form and I can see the correct value of items showing in the header counter and the mini cart item as expected.

However, when I click to remove the item in the mini cart I would like to reset the entire counter state back to 0 from whatever its previous value was and re-render the header and mini cart/cart item components so that the product item is hidden in the mini cart again.

Here is a basic run through of current components.

product-form.js

import React, { useState, useRef, useEffect } from "react";

export default function ProductForm(props) {
  const [count, setCount] = useState(0);

  const numberInput = useRef(null);

  useEffect(() => {
    numberInput.current.dispatchEvent(
      new Event("change", {
          detail: {
              newValue: count,
          },
          bubbles: true,
          cancelable: true,
      })
    );
  }, [count]);

  render(
    <>
     <div>
        <button onClick={() => {setCount(count - 1)}}>>
         Decrease
        </button>
       <Input 
        value={count}
        ref={numberInput}
        onChange={(e) => setNumberValue(e.target.value)}>
        <button onClick={() => {setCount(count   1)}}>>
         Increase
        </button>
     </div>
     <button onClick={event => props.onClick(() => event.currentTarget.parentNode.parentNode.querySelector('#product-qty-input').value)}>Add to Cart</button>
    </>
  )
}

header.js

import React, { useState } from "react";
import MiniCart from './minicart'

export default function HeaderRight({count}) {
  const [cartItemCount, setCartItemCount] = useState([]);
  return (
    <>
     <span>You have {count} items </span>
     <MiniCart count={count} />
    </>
  )
}

mini-cart.js

import CartItem from "./cartitem";
import CartEmpty from "./cartEmpty";

export default function MiniCart({count}) {
  return (
    <>
     { count == 0 && 
       <CartEmpty />
     }
     { count > 0 &&
        <CartItem count={count} />
     }
    </>
  )
}

mini-cart-item.js

export default function CartItem({count}) {
  return (
    <>
      <p>Product Name</p>
      <p>Unit Price: $125.00</p>
      <p>x{count}</p>
      <p>$375.00</p>
      <button>REMOVE ITEM</button>
    </>
  )
}

CodePudding user response:

Where does the count of HeaderRight come from? All the rendering logic comes from this value. If this is a state, pass down also its set function to CartItem. Then you can do the following with the button:

<button onClick={setCount(count - 1)}>REMOVE ITEM</button>

The CartItem component would look like:

export default function CartItem({count, setCount}) {
  return (
    <>
      <p>Product Name</p>
      <p>Unit Price: $125.00</p>
      <p>x{count}</p>
      <p>$375.00</p>
      <button onClick={setCount(count - 1)}>REMOVE ITEM</button>
    </>
  )
}

CodePudding user response:

This example extrapolates from your original question. In short it "lifts state up" to a parent component which controls how the flow of state affects its children: the MiniCart, and the form.

  1. It uses a form to capture the information which it places in a form state. When the form button is clicked a function checks to see if the item exists - if it does it increases its count, otherwise it adds the new item to the cart. cart is an array of item objects.

  2. You can increase the count of any existing cart items

  3. You can remove an item from the cart completely (rather than setting its count to zero.

const { Fragment, useEffect, useState } = React;

// For convenience have the Header return
// its children
function HeaderRight({ children }) {      
  return (
    <header>
      {children}
    </header>
  );
}

// MiniCart returns "Cart empty" if there are no
// items in the cart, otherwise it maps over
// the items renders a `CartItem` for each one
// Note that we've passed down the reference to
// handleRemove in each case
function MiniCart({ cart, handleRemove }) {
  if (!cart.length) return <div>Cart empty</div>;
  return (
    <ul>
      {cart.map(item => {
        return (
          <CartItem
            item={item}
            handleRemove={handleRemove}
          />
        );
      })}
    </ul>
  );
}

// We create an item, and attach the `handleRemove`
// reference to the button. When the button is clicked
// we pass the function the item id
function CartItem({ item, handleRemove }) {  
  return (
    <div className="item">
      <p>{item.name}</p>
      <p>{item.count}</p>
      <button onClick={() => handleRemove(item.id)}
      >Remove item
      </button>
    </div>
  );
}

function App() {

  // Initialise two states - one for the cart,
  // the other for the form
  const [ cart, setCart ] = useState([]);
  const [ form, setForm ] = useState({});

  // `handleRemove` updates the cart by
  // filtering out those objects that don't
  // match the id
  function handleRemove(id) {
    const filtered = cart.filter(obj => obj.id !== id);
    setCart(filtered);
  }

  // Get the `name` property from the form state
  // copy the cart, and then find the index of an existing
  // item in the cart when its name property matches the
  // form name. If it's found increase its count, otherwise
  // push the entire form into the cart, and update the cart state.
  function handleSubmit() {
    const { name } = form;
    const copy = [...cart];
    const index = copy.findIndex(obj => obj.name === name);
    if (index >= 0) {
        copy[index].count;
    } else {
      copy.push({ id: cart.length   1, count: 1, ...form });
    }
    setCart(copy);
  }

  // Handles the change in form state
  function handleChange(e) {
    const { name, value } = e.target;
    setForm({ ...form, [name]: value });
  }

  return (
    <main>
      <HeaderRight>
        <MiniCart cart={cart} handleRemove={handleRemove} />
      </HeaderRight>
      <form>
        <select name="name" onChange={handleChange}>
          <option selected disabled>Choose item</option>
          <option value="cabbage">Cabbage</option>
          <option value="broom">Broom</option>
          <option value="rabbit">Rabbit</option>
        </select>
        <button
          type="button"
          onClick={handleSubmit}
        >Add item
        </button>
      </form>
    </main>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

  • Related