How to use the value of `useSelector()` in click event handlers?

Viewed 868

Foll is a outline of the code. I am getting outdated 'cartSize'.

function CartComp(props) {
  const [cartSize, setCartSize] = useState(0);

const cartList = useSelector((state) => state.cart.cartItems);

useEffect(() => {
  setCartSize(() => {
    return cartList.length;
  });
}, [cartList]);

const onClickAddBtn = (cartObj) => {
  if (cartSize > 4) { // outdated state
    alert(`cannot add more than ${cartSize} items`);
  }
}
<button
onClick={onClickAddBtn.bind(this, cartObj)}
>
Add
</button>
}

basically, on clicking on , need to fetch cartSize for logical purpose. useSelector() gives an array cartList, whose length is set as setCartSize. so, when cartSize is accessed, it is giving value, one step behind. ie on clicking <button>, the cartList is latest but cartSize is the value from previous value.

how to manage this? How to use the value of useSelector() in click event handlers?

EDIT-1:

// robo-list.js

const cartList = useSelector((state) => state.robo.cartItems);

{galleryItems.map((robo) => {
 <div>
  <CartButton list={cartList} item={robo} />
 </div>
...

// robot-gallery.js

import { useDispatch } from "react-redux";

import Button from "@mui/material/Button";
import AddShoppingCartIcon from "@mui/icons-material/AddShoppingCart";

import { roboActions } from "../store/robo-slice";

function CartButton(props) {
  console.log("gal2: ",props.list.length);
  const dispatch = useDispatch();

  const onClickAddBtn = (len) => {
    console.log("gal1: ",len);
    if (props.list.length > 2) {
      alert(`cannot add more than ${props.cartList.length} material`);
    } else {
      dispatch(
        roboActions.updateCart({
          item: props.item,
          userAction: "addItemToCart",
        })
      );
    }
  };

  return (
    <Button
      variant="outlined"
      startIcon={<AddShoppingCartIcon fontSize="small" />}
      disabled={props.item.stock === 0}
      onClick={onClickAddBtn}
    >
      Add
    </Button>
  );
}
export default CartButton;

I have removed bind() and seperated click code into another file called robot-gallery.js. I am passing cartList and clicked item robo into the .

still cartList.length inside is one step behind. what am I missing here? Pls help.

full code is here https://codesandbox.io/s/trusting-goldberg-gcivk?file=/frontend/src/components/robo-list.js

EDIT 2: // robot-list.js

// robot-gallery.js

function CartButton(props) {
  const cartList = useSelector((state) => state.robo.cartItems);

  console.log("gal2: ",cartList.length); // 
  const dispatch = useDispatch();

  const onClickAddBtn = () => {
    console.log("gal1: ", cartList.length);
    if (cartList && cartList.length > 2) {
      alert(`cannot add more than ${cartList.length} items`);
    } else {
      dispatch(
        roboActions.updateCart({
          item: props.item,
          userAction: "addItemToCart",
        })
      );
    }
  };
  <Button
  variant="outlined"
  startIcon={<AddShoppingCartIcon fontSize="small" />}
  disabled={props.item.stock === 0}
  onClick={onClickAddBtn}
>
  Add
</Button>

cartList.length > 2 is one update behind.

2 Answers

Remove cartSize entirely. It's always going to be one render behind.

function CartComp(props) {
  const cartList = useSelector((state) => state.cart.cartItems);

  const onClickAddBtn = () => {
    if (cartList.length > 4) {
      alert(`cannot add more than ${cartList.length} items`);
    }
  };

  return <button onClick={onClickAddBtn}>
      Add
    </button>;
};

Why do you need to keep the length in sync with state? Can't you just do:

const CartComponent = () => {

  const cartList = useSelector((state) => state.cart.cartItems);

  const onClickAddBtn = () => {
    if(cartList && cartList.length > 4) alert(`cannot add more than ${cartList.length} items`);
  }
  return (
    <button onClick={onClickAddBtn}>
      Add
    </button>
  )
}

You should also check that redux state is actually updated. Are you perhaps mutating state? A good practice could be to use immer in your reducers, to make sure your state is immutable. If it is mutated, React might not pick up on state changes as the reference to the data is not changed.

Related