Jonathan Lightbringer
Jonathan Lightbringer

Reputation: 574

How to chain React Context dispatch? (Hooks)

What I want to do is sort of subscribe to a dispatch, then dispatch to another state. I've tried useEffect but it's giving me infinite loop and a few googling says to not update state on useEffect so now my code looks like this, but it's not working as intended.

const Chain = () => {
  const [fooState, fooDispatch] = fooOne() // useReducer
  const [barState, barDispatch] = barTwo() // useReducer

  const btnOnClick = () => {
    fooDispatch({ type: "UPDATE_FOO" })

    // Update barState base on the new value of foo, doesn't work. 
    // Foo is not yet updated.
    if (fooState.value == 'new value') {
      barDispatch({ type: "UPDATE_BAR" }) 
    }
    // fooState gets updated after this function block.
  }

  return <Button label="Button" onClick={btnOnClick} />
}

export default Chain

My useReducer code looks like this: https://kentcdodds.com/blog/how-to-use-react-context-effectively

On line if (fooState.value == 'new value') {, React still haven't updated the value so I can't go inside the if block.

Upvotes: 2

Views: 4204

Answers (2)

bamse
bamse

Reputation: 4373

I think you should use useEffect to keep the 2 reducer hooks in sync.

Hooks can be optimized to execute only when certain values change. In your case, when fooState.value (or fooState if you don't want to be too strict).

Something like:

React.useEffect(() => {
  if (fooState.value == 'new value') {
    barDispatch({ type: "UPDATE_BAR" }) 
  }
}, [fooState.value]);

should get you the behaviour you want - also remove barDispatch from btnOnClick

The executing order should be something like:

  1. The user clicks the button and you call fooDispatch
  2. The (foo)dispatch changes the fooState value which triggers a component re-render
  3. Use useEffect to take action when fooState changed and calls the barDispatch

If fooOne and barTwo are frequently used together maybe you should combine them in a single reducer hook.

I don't know of any reason why you shouldn't set state (or other hooks) in useEffect. It is a bit tricky because you could get an infinite loop but there are cases where you need this.

I hope this helps!

Upvotes: 6

David Bradshaw
David Bradshaw

Reputation: 13077

Calls to dispatch are asynchronous, so you can not read the state until the next rerender.

Also you should dispatch an action that says what happened, not what you want to happen, if that makes sense. Your reducer should conceptually react to the user interaction or other event that has just taken place. This is why it is called React.

So what this means is the instead of dispatching UPDATE_FOO, you should dispatch BTN_CLICKED and then both the FOO and BAR reducers should listen for this action and decide if they need to update.

Upvotes: 1

Related Questions