简体   繁体   中英

UseState called by UseEffect doesn't update the variable using the Set method

Consider the code :

import React, { useState, useEffect } from 'react';
........ More stuff
const ProductContext = React.createContext();
const ProductConsumer = ProductContext.Consumer;


const ProductProvider = ({ children }) => {
  const [state, setState] = useState({
    sideBarOpen: false,
    cartOpen: true,
    cartItems: 10,
    links: linkData,
    socialIcons: socialData,
    cart: [],
    cartSubTotal: 0,
    cartTax: 0,
    cartTotal: 0,
    .......
    loading: true,
    cartCounter: 0,
  });
  
   const getTotals = () => {
    // .. Do some calculations .... 
    
    return {
      cartItems,
      subTotal,
      tax,
      total,
    };
  };
  
  
 const addTotals = () => {
    const totals = getTotals();
    setState({
      ...state,
      cartItems: totals.cartItems,
      cartSubTotal: totals.subTotal,
      cartTax: totals.tax,
      cartTotal: totals.total,
    });
  };
  
  
   /**
   * Use Effect only when cart has been changed
   */
  useEffect(() => {
    if (state.cartCounter > 0) {
      addTotals();
      syncStorage();
      openCart();
    }
  }, [state.cartCounter]);
  
  
  
  
  ..... More code 
  
  
  
    return (
    <ProductContext.Provider
      value={{
        ...state,
       ............... More stuff 
      }}
    >
      {children}
    </ProductContext.Provider>
  );
};

export { ProductProvider, ProductConsumer };

This is a Context of a Shopping cart ,whenever the user add a new item to the cart this piece of code runs :

  useEffect(() => {
    if (state.cartCounter > 0) {
      addTotals();
      syncStorage();
      openCart();
    }
  }, [state.cartCounter]);

And updates the state , however the setState function doesn't update state when running :

 setState({
      ...state,
      cartItems: totals.cartItems,
      cartSubTotal: totals.subTotal,
      cartTax: totals.tax,
      cartTotal: totals.total,
    });

Inside addTotals , even though this function is being called automatically when UseEffect detects that state.cartCounter has been changed.

Why aren't the changes being reflected in the state variable ?

Without a stripped down working example, I can only guess at the problems...

Potential Problem 1

You're calling a callback function in useEffect which should be added to it's [dependencies] for memoization.

const dep2 = React.useCallback(() => {}, []);

useEffect(() => {
   if(dep1 > 0) {
     dep2();
   }
}, [dep1, dep2]);

Since dep2 is a callback function, if it's not wrapped in a React.useCallback , then it could potentially cause an infinite re-render if it's changed.

Potential Problem 2

You're mutating the state object or one of its properties. Since I'm not seeing the full code, this is only an assumption. But Array methods like: splice , push , unshift , shift , pop , sort to name a few cause mutations to the original Array. In addition, objects can be mutated by using delete prop or obj.name = "example" or obj["total"] = 2 . Again, without the full code, it's just a guess.

Potential Problem 3

You're attempting to spread stale state when it's executed. When using multiple setState calls to update an object, there's no guarantee that the state is going to be up-to-date when it's executed. Best practice is to pass setState a function which accepts the current state as an argument and returns an updated state object:

setState(prevState => ({
  ...prevState,
  prop1: prevState.prop1 + 1
}));

This ensures the state is always up-to-date when it's being batch executed. For example, if the first setState updates cartTotal: 11 , then prevState.cartTotal is guaranteed to be 11 when the next setState is executed.

Potential Problem 4

If state.cartCounter is ever updated within this component, then this will cause an infinite re-render loop because the useEffect listens and fires every time it changes. This may or may not be a problem within your project , but it's something to be aware of. A workaround is to trigger a boolean to prevent addTotals from executing more than once. Since the prop name "cartCounter" is a number and is rather ambiguous to its overall functionality, then it may not be the best way to update the cart totals synchronously.

  React.useEffect(() => {
    if (state.cartCounter > 0 && state.updateCart) {
      addTotals();
      ...etc
    }
  }, [state.updateCart, state.cartCounter, addTotals]);

Working demo (click the Add to Cart button to update cart state):

编辑 busy-keller-4fl46


If neither of the problems mentioned above solves your problem, then I'd recommend creating a mwe . Otherwise, it's a guessing game.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM