1

I am using a context like the following:

const placeCurrentOrder = async () => {
    alert(`placing order for ${mealQuantity} and ${drinkQuantity}`)
}

<OrderContext.Provider
  value={{
    placeCurrentOrder,
    setMealQuantity,
    setDrinkQuantity,
  }}
>

and I'm calling this context deep down with something like this (when the user clicks a button):

const x = () => {
  orderContext.setMealQuantity(newMealQuantity)
  orderContext.setDrinkQuantity(newDrinkQuantity)
  await orderContext.placeCurrentOrder()
}

Sort of like I expect, the state doesn't update in time, and I always get the previous value of the state. I don't want to have a useEffect, because I want control over exactly when I call it (for example, if mealQuantity and drinkQuantity both get new values here, I don't want it being called twice. The real function is far more complex.)

What is the best way to resolve this? I run into issues like this all the time but I haven't really gotten a satisfactory answer yet.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Dara Java
  • 2,410
  • 3
  • 27
  • 52
  • this might help https://stackoverflow.com/questions/64259890/react-usecontext-value-is-not-updated-in-the-nested-function. Stale closure issue it seems. – Sangeet Agarwal Jan 08 '22 at 03:58

1 Answers1

1

You can set them in a ref. Then use the current value when you want to use it. The easiest way is probably to just create a custom hook something like:

const useStateWithRef = (initialValue) => {
  const ref = useRef(initialValue)
  const [state, setState] = useState(initialValue)

  const updateState = (newState) => {
    ref.current = typeof newState === 'function' ? newState(state) : newState
    setState(ref.current)
  }

  return [state, updateState, ref]
}

then in your context provider component you can use it like:

const [mealQuantity, setMealQuantity, mealQuantityRef] = useStateWithRef(0)
const [drinkQuantity, setDrinkQuantity, drinkQuantityRef] = useStateWithRef(0)

const placeOrder = () => {
  console.log(mealQuantityRef.current, drinkQuantityRef.current)
}

You can also just add a ref specifically for the order and then just update it with a useEffect hook when a value changes.

const [drinkQuantity, setDrinkQuantity] = useState(0)
const [mealQuantity, setMealQuantity] = useState(0)
const orderRef = useRef({
  drinkQuantity,
  mealQuantity
})

useEffect(() => {
  orderRef.current = {
    drinkQuantity,
    mealQuantity,
  }
}, [drinkQuantity, mealQuantity])

const placeOrder = () => {
  console.log(orderRef.current)
}
Steve K
  • 8,505
  • 2
  • 20
  • 35
  • 1
    That's cool, and I'm sure it will work, but is it idiomatic? – Dara Java Jan 08 '22 at 04:45
  • 1
    The useRef hook is designed to have a way to access a value that will persist renders. So if you are not going to display the value on screen and need to persist renders then you will more than likely be using the useRef hook. This is one of the reasons I no longer really like using context for state management. I usually just do a custom hook for state management now like https://github.com/SteveKanger/react-global-store-hook. But anyway I also edited the answer with another way to go about it that may be a little more of a strait forward approach. – Steve K Jan 08 '22 at 15:06
  • 1
    Your initial way of doing it is good, and I've used it before, I just never thought of using it in Context. It just seems kind of wrong to me and I wish useEffect just worked naturally how you'd expect. It seems to be a design flaw because I can't see how this behaviour could be useful. What's the problem with contexts though? I took a look at your link and it seems like just a global store. Not very organised. You can nest contexts which leads to a good logical separation, and you can set different values based on where your component is rendered. What other problems do you have with context? – Dara Java Jan 09 '22 at 01:08
  • There are a lot of things wrong with contexts. Most notably performance and that the changing of information causes re renders in all attached components. Also the global store hook is way more organized not less than a context and it is easier at scale. Why do you think big companies use Redux its very similar. Logic can be separated into small functions (actions in Redux with thunk). With context you end up with tons of Provider components or ones that are huge and full of logic. – Steve K Jan 09 '22 at 01:37
  • That's cool thanks. Well I only started using them and you're right, my app's performance is degrading lol. I didn't think it could be the contexts. Do you have any reading materials on the way you do it? If not, you should write a post on it! – Dara Java Jan 10 '22 at 23:02