2

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])

SteveW
  • 337
  • 4
  • 11

2 Answers2

1

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;
}

Prateek Thapa
  • 4,829
  • 1
  • 9
  • 23
  • Thanks Prateek! I like the useRef idea, I am not sure what you mean by use a variable in your dependency. The dependency needs to be the global state object. – SteveW Oct 06 '20 at 03:44
  • If you need `globalState` to be a dependency you need to change your `useEffect` callback to not alternate globalState every render. That is what is causing the loop. – xtr Oct 06 '20 at 03:47
  • Yes, I wrote that too. Also I have put a ref to check whether restricts from dispatching the second time. – Prateek Thapa Oct 06 '20 at 03:51
  • 1
    I want the effect to be dependent of the current global state - which it is. I then want to update the global state from the API result. But I need an escape hatch to stop the loop. The Ref idea Prateek suggested seems great! – SteveW Oct 06 '20 at 03:57
  • 1
    Yeah from your answer to his initial answer it wasn't clear to me you understood fully what was causing the issue so I wanted to reiterate on that. – xtr Oct 06 '20 at 03:59
  • Thanks @xtr! I just wanted to get some perspective as I am new to React and it's nuances. I had thought that some local state would work. – SteveW Oct 06 '20 at 04:02
  • My solution is a workaround. This kind of problem stems from the poor architectural decisions about the storage of data. Since you're new, you could use it. The more you develop, you won't be facing this kind of issue again. Till then, use a hack, improve on it later. – Prateek Thapa Oct 06 '20 at 04:04
0

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.

xtr
  • 133
  • 8