Search code examples
javascriptreactjsreact-hooksuse-reducer

How to avoid getInitialState() function used in useReducer( reducer, getInitialState() ) from running on every render?


See the snippet below.

By doing this:

React.useState(()=>getInitialState2());

You avoid running the getInitialState2() used in the useState on every render.

But that doesn't seem to work on the useReducer hook.

QUESTION

Is there a way to avoid running a function that is used in the initialState parameter on the useReducer hook on every render?

function App() {
  
  const [state,dispatch] = React.useReducer(reducer,getInitialState());
  const [state2,setState2] = React.useState(()=>getInitialState2());
  
  return(
    <React.Fragment>
      <div>
        State: {state}
      </div>
      <div>
        State2: {state2}
      </div>
      <div>
        <button onClick={() => dispatch({type: "INCREMENT"})}>+</button>
        <button onClick={() => dispatch({type: "DECREMENT"})}>-</button>
      </div>
    </React.Fragment>
  );
}

function getInitialState() {
  console.log("From getInitialState...");
  return 0;
}

function getInitialState2() {
  console.log("From getInitialState2...");
  return 0;
}

function reducer(state,action) {
  switch(action.type) {
    case "INCREMENT": {
      return state + 1;
    }
    case "DECREMENT": {
      return state - 1;
    }
    default: {
      return state;
    }
  }
}

ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>


Solution

  • You are running it on every render by calling it. To make the initialisation lazy, pass a function. In addition, the lazy init is the 3rd param of useReducer. Whatever you supply as the 2nd param of useReducer will be passed to the lazy init function, but you can ignore it.

    const [state,dispatch] = React.useReducer(reducer, null, getInitialState);
    

    Or wrap it with an arrow function, if you need to initialize it with a value from props:

    const [state,dispatch] = React.useReducer(reducer, null, () => getInitialState(props.something));
    

    Demo:

    function App() {
      
      const [state,dispatch] = React.useReducer(reducer,null,getInitialState);
      const [state2,setState2] = React.useState(()=>getInitialState2());
      
      return(
        <React.Fragment>
          <div>
            State: {state}
          </div>
          <div>
            State2: {state2}
          </div>
          <div>
            <button onClick={() => dispatch({type: "INCREMENT"})}>+</button>
            <button onClick={() => dispatch({type: "DECREMENT"})}>-</button>
          </div>
        </React.Fragment>
      );
    }
    
    function getInitialState() {
      console.log("From getInitialState...");
      return 0;
    }
    
    function getInitialState2() {
      console.log("From getInitialState2...");
      return 0;
    }
    
    function reducer(state,action) {
      switch(action.type) {
        case "INCREMENT": {
          return state + 1;
        }
        case "DECREMENT": {
          return state - 1;
        }
        default: {
          return state;
        }
      }
    }
    
    ReactDOM.render(<App/>, document.getElementById("root"));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
    <div id="root"/>