React State Infinite Loop with LocalStorage and DB

824 views Asked by At

I am developing an eComm site where I persist the user's cart to global state and to the browser's local storage.

When they head to the Cart or Checkout page, I take the global state and send it to an API for verification and get back the "true" state of the cart. I then want to update the global state to reflect this API data.

So the App mounts, checks for local storage and creates the global state from this. On the Cart and Checkout pages, I have a useEffect listening to a global useContext for global state changes.

I cant use on mount as the local storage hasn't loaded by this stage

useEffect(()=>{}, [])

How can I avoid the loop this creates?

Here is the gist of the useEffect and dependency.

TIA!

const globalState = useGlobalState()
const dispatch = useDispatchGlobalState()

useEffect(() => {

        if (globalState.cart.length > 0) {
            
            fetch('/api...', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(globalState.cart)
            })
                .then((response) => {
                    return response.json()
                })
                .then((dbcart) => {
                    dispatch({
                        type: 'update global state',
                        payload: {
                            cart: dbcart
                        }
                    })

                })
                .catch((error) => {
                   ...
                })

        }

    }, [globalState])

2

There are 2 answers

7
Prateek Thapa On

Your globalState is an object. Upon updating that object via dispatch changes the references to that object. Since your object is in the dependency array of your useEffect. useEffect runs again causing a triggering multiplie re-renders.

Make sure to use a variable as a dependency in your useEffect.

Also, you could use a ref and updated the ref when the dispatch is done. Upon next re-render, you could check for the ref.

function App() {
  const globalState = useGlobalState();
  const dispatch = useDispatchGlobalState();

  const hasUpated = React.useRef(false);

  useEffect(() => {
    if (globalState.cart.length > 0 && !hasUpdated.current) {
      fetch("/api...", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify(globalState.cart)
      })
        .then(response => {
          return response.json();
        })
        .then(dbcart => {
          hasUpated.current = true;
          dispatch({
            type: "update global state",
            payload: {
              cart: dbcart
            }
          });
        });
    }
  }, [globalState]);
  return null;
}

0
xtr On

To quote the React docs:

Conditionally firing an effect

The default behavior for effects is to fire the effect after every completed render. That way an effect is always recreated if one of its dependencies changes. However, this may be overkill in some cases, like the subscription example from the previous section. We don’t need to create a new subscription on every update, only if the source prop has changed.

The function you provided alters globalState every render so every render triggers another render so it loops.