Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the purpose of the 3rd argument in useReducer?

From the docs:

[init, the 3d argument] lets you extract the logic for calculating the initial state outside the reducer. This is also handy for resetting the state later in response to an action.

And the code:

function init(initialCount) {
  return { count: initialCount };
}

function reducer(state, action) {
  switch (action.type) {
    ...
    case 'reset':
      return init(action.payload);
    ...
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  ...
}

Why would I do that over reusing a constant initialState?

const initialState = {
  count: 5,
};

function reducer(state, action) {
  switch (action.type) {
    ...
    case 'reset':
      return initialState;
    ...
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialState);
  ...
}

Looks less verbose to me.

like image 889
Paul Razvan Berg Avatar asked Nov 25 '19 22:11

Paul Razvan Berg


People also ask

How many arguments does useReducer have?

1. useReducer() The useReducer(reducer, initialState) hook accept 2 arguments: the reducer function and the initial state.

What is the purpose of the useReducer hook?

The useReducer Hook accepts two arguments. The reducer function contains your custom state logic and the initialState can be a simple value but generally will contain an object. The useReducer Hook returns the current state and a dispatch method.

Why do we need a useReducer?

The useReducer hook is usually recommended when the state becomes complex, with state values depending on each other or when the next state depends on the previous one. However, many times you can simply bundle your separate useState statements into one object and continue to use useState .

Can useReducer to manage state objects that contain multiple sub-values?

Another option is useReducer, which is more suited for managing state objects that contain multiple sub-values.


3 Answers

EDIT july 2020: React documentation has now better explainations on this arg called lazy initializer. Using this function in another way could result to breaking changes due to undocumented effect. Following answer remain valid.


As far as I can experiment, the init function as third arg is a transformer of the initialState.

It means that initialState will not be used a initial state, but as arg for init function. The return of this one will be the true initialState. It could be usefull to avoid huge param during the useReducer initialization line.

/* Here is the magic. The `initialState` pass to   * `useReducer` as second argument will be hook  * here to init the real `initialState` as return  * of this function  */ const countInitializer = initialState => {   return {     count: initialState,     otherProp: 0   }; };  const countReducer = state => state; // Dummy reducer  const App = () => {   const [countState /*, countDispatch */] =     React.useReducer(countReducer, 2, countInitializer);    // Note the `countState` will be initialized state direct on first render   return JSON.stringify(countState, null, 2); }  ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
like image 101
HollyPony Avatar answered Oct 19 '22 10:10

HollyPony


My understanding is that the lazy initialization is designed for special situations that the code initializing the state is memory-intensive or CPU-intensive so the developer wants to keep the scope of the state data inside the component.

For example, if you are going to design a PhotoPane component which holds a high definition photo for editing.

const PhotoPane = (props) => {
    const initialPixelData = loadPhoto(props.photoID);
    const [pixelData, dispatch] = useReducer(reducerFunc, initialPixelData);
    ...
}

Above code has a serious performance issue because loadPhoto() is repeatedly called. If you don't want to load the photo again every time when the component renders, the intuitive reaction is to move the loadPhoto(props.photoID) out of the component. But it will cause another problem. You will have to load all photos into memory in Context or somewhere else and it will definitely create a memory hog.

So this is the time for us to introduce lazy initialization. Please check out the code below.

const PhotoPane = (props) => {
    const init = (photoID) => loadPhoto(photoID);
    const [pixelData, dispatch] = useReducer(reducerFunc, props.photoID, init);
    ...
}

The init() function is executed exactly only one time when the useReducer is first called.

Actually useEffect() hook can achieve a similar result. But lazy initialization is still the most direct solution.

like image 43
George Lei Avatar answered Oct 19 '22 10:10

George Lei


useReducer accepts an optional third argument, initialAction. If provided, the initial action is applied during the initial render.

For example:

function Counter({ initialCount }) {
  const [state, dispatch] = useReducer(reducer, initialState, {
    type: "reset",
    payload: initialCount
  });

As you can see, the third parameter is an initial action to be performed, applied during the initial render.

For Example: Codesandbox Example Link

like image 21
developerKumar Avatar answered Oct 19 '22 09:10

developerKumar