3

Let's take following example with an inline reducer:

const App = () => {
  const [state, dispatch] = React.useReducer(s => {
    console.log("reducer invoked with state", s)
    return s + 1
  }, 0);

  return (
    <div>
      <p>{state}</p>
      <button onClick={dispatch}>Increment</button>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>

On first click, App is rendered twice, subsequent clicks cause only one re-render.

I would like to understand above behavior of useReducer. After having read this answer, some things are still not clear to me.

  1. Why is a double render triggered only first time for useReducer?
  2. Are there additional circumstances to 1.), which cause double rendering for an inline reducer as well?
  3. When would this inline reducer pattern be not recommended (performance, etc.)? Or is it without flaws?
bela53
  • 2,320
  • 9
  • 24

1 Answers1

1

By putting the reducer function inside the component, you make it so each time the component renders a new function is created. It may have the same text, but react can't tell that, so it's forced to run the old and new functions and compare their results.

In the following snippet i've made the reducer function log out which instance of the function is running, and you'll see that those first two double-calls are two different functions. I'm not sure why it doesn't need to also do this on later times

let count = 0;
const App = () => {
  count++;
  const [state, dispatch] = React.useReducer(s => {
    console.log(`reducer #${count} invoked with state`, s)
    return s + 1
  }, 0);

  return (
    <div>
      <p>{state}</p>
      <button onClick={dispatch}>Increment</button>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>

So yes, using an inline reducer can reducer performance a little bit. As a result, if you don't need to inline it, then you should move it outside the component.

If you do need to inline it (say, because you need to refer to other variables inside the component), it's probably not a big deal in most cases. If it is, then you can useCallback to memoize it and only change it when needed.

const Example = () => {
  const [size, setSize] = useState(1);
  const reduction = useCallback((s) => {
    console.log(`reducer called; something = ${something}`);
    return s + size;
  }, [something]l
  const [state, dispatch] = useReducer(reduction, 0);
Nicholas Tower
  • 56,346
  • 6
  • 64
  • 75
  • Thanks for the answer! I am still curious about a more in-depth explanation to question 1 and 2. – bela53 May 21 '20 at 18:16