UseReducer — A Side Note to the Official Doc

The following can be read as a side reference to the official React Doc

Image for post
Image for post
Photo by Annie Spratt on Unsplash

UseReducer

What is a reducer? For people like me who haven’t get introduced to Reduct, think that the reducer is a pure function that takes the previous state and an action, and returns the next state.

(previousState, action) => nextState

In a more complex app, you’re going to want different entities to reference each other. We suggest that you keep your state as normalized as possible, without any nesting. Keep every entity in an object stored with an ID as a key, and use IDs to reference it from other entities, or lists. Think of the app’s state as a database. For example, keeping todosById: { id -> todo } and todos: array<id> inside the state would be a better idea in a real app.

A reducer helps you to manage these in-app states. It’s called a reducer because it’s the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue).

It's very important that the reducer stays pure. Things you should never do inside a reducer:

  • Mutate its arguments;
  • Perform side effects like API calls and routing transitions;
  • Call non-pure functions, e.g. Date.now() or Math.random().

Given the same arguments, it should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation

function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})

default:
return state
}
}

Note that:

We don’t mutate the state. We create a copy with Object.assign(). Object.assign(state, { visibilityFilter: action.filter }) is also wrong: it will mutate the first argument. You must supply an empty object as the first parameter. You can also enable the object spread operator proposal to write { ...state, ...newState } instead.

We return the previous state in the default case. It's important to return the previous state for any unknown action.

If you return the same value from a Reducer Hook as the current state, React will bail out without rendering the children or firing effects.

Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree.

We have actions in the reducer that comes with a type, but what about payload? It’s basically a key in your action which stores the data you want to pass to the reducers.

To separate this type from regular data the payload property is used. Now, on what should go into payload and what should be on the same level with it is debatable, but a popular standard is the Flux Standard Action which states that among the official requirements you may add a payload, error, and meta property. Here the payload is defined as:

The optional payload property MAY be any type of value. It represents the payload of the action. Any information about the action that is not the type or status of the action should be part of the payload field. By convention, if error is true, the payload SHOULD be an error object.

Here’s a step-by-step break down of useReducer hook in React:

  • Set initial state

There are two different ways to initialize useReducer state. You may choose either one depending on the use case. The simplest way is to pass the initial state as a second argument:

const [state, dispatch] = useReducer(
reducer,
{count: initialCount} );
  • Create your reducer function (can have logic going in as long as stays pure)
const reducer = (state, action) => {
if (action.type === GRUDGE_ADD) {
return [action.payload, …state];
}
if (action.type === GRUDSGE_FORGIVE) {
return state.map(grudge => {
if (grudge.id !== action.payload.id) return grudge;
return { …grudge, forgiven: !grudge.forgiven };
});
}
return state;
};
  • Patch your reducer with your state
const [grudges, dispatch] = useReducer(reducer, initialState);
//grudges is your state
  • Dispatch your action when you need to change state (optional payload can be passed in), and return new state
const addGrudge = useCallback(({ person, reason }) => {
dispatch({
type: GRUDGE_ADD,
payload: {
person, reason, forgiven: false, id: id()
}

});
}, [dispatch]);
const toggleForgiveness = useCallback(id => {
dispatch({
type: GRUDSGE_FORGIVE,
payload: {id}});

}, [dispatch]);
Image for post
Image for post
a simpler example without payload

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store