Implement The State Reducer Pattern Using The useReducer React Hook

Share this video with your friends

Send Tweet
Published 3 years ago
Updated 2 years ago

One interesting pattern that you can implement by using the useReducer hook is the state reducer

This pattern - initially developed by Kent C. Dodds - is a way to implement an inversion of control mechanism, that is a way to allow the users of the component to tap into some internal logic of the same.

For this component, you can use this pattern to allow the user to control the initial state of the Wizard and add a custom action to the reducer pattern, thus giving even more flexibility when using the component.

In this lesson, you'll update the component API to implement this pattern allowing the user of the component to create its reducer function or even new actions or control the internal actions of the component.

[0:00] MatÃas Hernández: Now that our component used the useReducer hook, we can apply the state reducer pattern. The idea is to allow the user to access the component's state. For this, we will first define the necessary API.

[0:11] We will allow the user to define the initial state and a custom reducer that will be passed as a prop. In the initial state, we will define that the active index will be number two, that is the third page.

[0:24] Under reducer, we'll capture the next page action, and we'll simply write to the console and finally return this directly. We will pass these values as props in our main component.

[0:36] Reducer will be reducer, and now, in the definition of our main component, we will receive these new props by adding reducer and initial state to the list. We will pass these values to the useReducer hook directly.

[0:50] We will rename this initial state to the full initial state to avoid naming confusions. We can see that our component now starts on the third page as expected, but when we click the back button, nothing happens. This is because we are using the user-defined reducer, which does not modify the state.

[1:09] What we need is that our reducer call the full reducer continues to work, and also takes the user's reducer. That is, we need to combine both functions. To achieve this, we will create a simple function that will combine the call to these reduced functions.

[1:26] We will call this new function combined reducers. Combined reducers will accept an undefined number of functions. We will use the spread syntax and return a new reducer that receives a state and an action.

[1:40] Now, the value of reducers that we passed as an argument is an array. We can use the array.reduce() method to combine its elements. Array.reduce() takes two arguments, a reducer function and an initial value.

[1:55] The reducer function has two arguments in turn, the accumulator and the current element that we will call next reducer. The initial state of array.reduce() will, in this case, be the value of a state. Here, what we'll return will be the new state created by the current reducer.

[2:13] That is, we will execute nextReducer with the current state indicated by the accumulator and the action with which it was called. Now we can use this function to combine our reducers.

[2:24] We will replace the use of the reducer prop here with a call to combine reducers that will first receive our internal reducer, call it default reducer, and then the user's reducer. Now our component works correctly, but we still have one case to solve.

[2:42] What happens if the user doesn't use these props? We have an error. This is because the reducer prop is not defined. We can solve this by simply assign it a default value that we will call default reducer. This reducer must be a simple reducer, but one that doesn't duplicate the logic that we already have.

[3:06] To avoid this duplication of logic, let's rename our current default reducer as whistle reducer, and create a new default reducer as a simple function that receives a state and an action, and returns the state directly. We still have one more error.

[3:23] That is because initial state is not defined either. We can pass a default value which solves the problem, but what if the user adds an initial state? In this case, we don't see the problem because our state is simple, but the user-defined state override ours.

[3:39] Here, a good idea is to combine the default state with the user-defined one. For that, we will use the spread syntax in default initial state, and in the initial state prop, expanding their values. Finally, we change the default value of the prop to an empty object.

[3:56] Now our component allows the user to access the state, passing an initial state value and a new reduced function. This captures the different actions. By clicking the next button, we can see the user's reducer in action.

[4:07] We also allow the user to define the initial state of our component by exposing the initial state prop. This design pattern is known as the state reducer, and it's a form of the inversion of control pattern.

Lucas Pereira Caixeta
Lucas Pereira Caixeta
~ a year ago

I guess this video became a bit complex with the combineReducer. Would love to read/see more about it in details, more examples :)

Zac Jones
Zac Jones
~ a year ago

Hey @Lucas Pereira Caixeta, the examples in the new React docs on useReducer might help: