3

I'm not sure if this is expected behavior, but if you bail out of a dispatch (https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-dispatch) while using the useReducer hook, the action occurs twice if it's followed by a render. Let me explain:

// bailing out to prevent re-rendering
const testReducer = (state, action) => {
  switch (action.type) {
    case "ADD":
      state.test += 1
      return state;
  }
};

const myComponent = () => {
  let [totalClicks, setClicks] = useState(0);
    const [state, setState] = useReducer(testReducer, {
      test: 0,
    });

  const clickHandler = () => {
    setState({type: 'ADD'});
    setClicks((totalClicks += 1));
  };

  return (
    <div>
      <button onClick={clickHandler}>+</button>
      <p>{totalClicks}</p>
      <p>test count: {state.test}</p>
    </div>
  );
}

When you click the button, state.test goes up by 2, while totalClicks increases by 1. However, if I were to change the reducer so that it doesn't bail to one like below, they would both increase by 1.

// non-bailing reducer
const testReducer = (state, action) => {
  switch (action.type) {
    case "ADD":
      return {
        test: state.test + 1,
      };
  }
};

Why is this? And is this expected behavior or a bug? sandbox example: https://codesandbox.io/s/sad-robinson-dds63?file=/src/App.js


UPDATE: after doing some debugging, it looks like this behavior only occurs when wrapped with a React.StrictMode

Does anyone know what causes this???

webbyweb
  • 385
  • 2
  • 11
  • 1
    Can you create a reproducible example for this. I created one and didn't find any issue. https://codesandbox.io/s/hooks-state-non-pure-update-d83uo – chandan_kr_jha May 04 '20 at 04:44
  • Attached. After looking at yours I don't understand the difference of what I was doing and why mine is behaving this way. https://codesandbox.io/s/sad-robinson-dds63?file=/src/App.js – webbyweb May 04 '20 at 17:20

1 Answers1

1

According to the doc of StrictMode, react intentionally calls reducer function with same action twice, so to expose unnoticed potentially harmful side-effects, which is exactly what happens to your case.

Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions: […] Functions passed to useState, useMemo, or useReducer

hackape
  • 18,643
  • 2
  • 29
  • 57
  • Wow that's interesting. So what about the example is unsafe? Is it the fact that we're updating our useState state and our useReducer state on the same click event? – webbyweb May 04 '20 at 18:12
  • It’s about you violating the presumption that reducer should be side-effect free, idempotent, and always returns new value when there’s a change. You break the 3rd rule (intentionally though). To me that’s more like a convention to begin with. But since there’s a whole bunch of ecosystems and mechanisms (like the bailout) built around this presumption, it becomes a requirement. So strictMode decides to expose it. – hackape May 04 '20 at 19:46