Beginner's Guide to React useReducer Hook
This final lesson eschews all external redux libraries and swaps in the use of React's built-in useReducer Hook and Context tools.
In this third and final lesson of the module, we’re going to go over another redux alternative, this one using no external libraries. Instead, we’ll be using a combination of React’s own useReducer
Hook and the built-in Context system to pass both state
and a dispatching function to various components.
You may remember we briefly introduced the useReducer
Hook in our React Hooks Deep Dive module, but said we’d be covering it later on. Well that time has come, so let’s get learning!
By the way, I also have a great article on this subject available on my own website if you’d like to see more examples of the
useReducer
Hook in action.
Using useReducer and Context#
The useReducer
Hook is provided by React natively without any third-party requirements. It’s quite a simple Hook to use, with its implementation looking like this:
xxxxxxxxxx
const [state, dispatch] = useReducer(reducer, initialArg, init);
You call the Hook, passing in a reducer function, initial state value (shown here as initialArg
) and an optional third argument, init
, which would be a function that allows you to lazy load your initial state value.
What the Hook returns is an array containing the current state
of the app and a dispatch
function to trigger state
updates via actions, just as we have been doing so far.
The useReducer
Hook focuses on what it does so well, but we still have a few missing pieces of the puzzle. For example, we no longer inherently have any central store mechanism, nor an obvious means to pass access to our app’s state
or the dispatch
function to different components. It’s not the end of the world, but it's common practice to create a centralized "store" mechanism just like we’ve seen with the other redux approaches this far, and then pass both the state
and dispatch
items across our app via React’s Context system.
Updating the project#
We have a few changes to make to our project again, starting with removing the React Redux libraries, and working our way through the same files from the last lesson to switch over to using the useReducer
Hook.
Before we dive in, I’m just going to give you a heads-up that when we’re done, the resulting code will look a lot more like the first lesson’s code than the much more concise approach offered in the last lesson using Redux Toolkit.
Personally I feel that the useReducer
and Context approach is somewhere between the two lessons we’ve covered so far. It is not as concise or smooth as that offered by the Redux Toolkit, but it does offer a much less opinionated approach, and doesn’t depend on any external libraries.
What’s more, the way we're going to tackle it here is one of a few different ways you could employ the useReducer
Hook.
At its simplest it is a single line function call that is paired with a switch
statement that updates some object values in a state
item.
We’re going to be taking it up a gear in this lesson by creating:
a central redux store
a Context provider wrapper
and a combined reducer function just like we’ve already seen, to keep things equal between the three lessons.
If you decide that your project needs a redux system it's up to you to decide which type of implementation you prefer. There's no right or wrong solution or indeed a one-solution-fits-all approach. Sometimes, using the built-in offerings of
useReducer
and Context that we're exploring here will be enough. If you prefer a more opinionated and structured approach then the Redux Toolkit will work best for you. Once we've finished the module you'll have a more informed set of choices that you can explore using your own ideas and projects. I'd highly recommend playing around with them in a code playground, such as CodeSandbox.io, to find which works best for you.
Switching up dependencies#
Just like the last lesson, let’s start by removing what we don’t need from our project. In this case, we’re going to remove the two packages, @reduxjs/toolkit
and react-redux
.
This time I’m just going to issue the yarn command, yarn remove @reduxjs/toolkit react-redux
.
With that done, let’s start with some of the foundational work and build the changes up from there.
Removing configureStore.js#
Dead easy one to start with - find the /config
folder that contains the configureStore.js
file and just delete it altogether. We’ll still be creating the idea of a store, but we’ll do the work in our central reducers.js
file this time.
This folder and file would be unused dead weight, so get it deleted and let’s move on.
Editing index.js#
We’re beginning our edits with the index.js
file this time as there’s very little to change and it’s largely all removals.
Open up the file and let’s change up the imports around redux things:
xxxxxxxxxx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
// Reducer store
import { StoreProvider } from './reducers/reducers';
Notice that the top imports are the same, but we’ve removed the store
we brought in previously from our configureStore.js
file which no longer exists.
We’ve also changed the Provider
import to StoreProvider
and changed the import location from react-redux
to our reducers.js
file. We’ll build out this new StoreProvider
component later, but let’s import it here and use it right now.
With our StoreProvider
import at the ready, simply use it to directly replace the <Provider>
component that’s currently wrapping our <App/>
component.
The changed file should look like this:
xxxxxxxxxx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
// Reducer store
import { StoreProvider } from './reducers/reducers';
ReactDOM.render(
<React.StrictMode>
<StoreProvider>
<App />
</StoreProvider>
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Editing eventReducer.js#
Let’s move onto the eventReducer.js
file. Open it up and familiarize yourself with the current code we used alongside the Redux Toolkit. Unfortunately, our new code won’t look quite as neat and tidy as this, but it won’t be as long and unwieldy as in the first lesson.
Remove everything in this file except for the uuid
import at the top.
Now, we won’t need to create any action functions this time, but we will be reinstating our action types. Let’s create the simple JavaScript key:value object now:
xxxxxxxxxx
import uuid from 'react-uuid';
// Actions
export const actions = {
ADD_ATTENDEE: 'add event attendee',
TOGGLE_ATTENDANCE: 'change attendance',
DELETE_ATTENDEE: 'delete attendance'
};
There we go, pretty much identical to those from the first lesson. Next, let’s create our eventReducer
variable, complete with switch
statement to work through the possible action types, and export it from the file as the default export.
xxxxxxxxxx
// Reducer
const eventReducer = (state, action) => {
switch (action.type) {
case actions.ADD_ATTENDEE: {
const newAttendee = action.payload;
newAttendee.id = uuid();
newAttendee.attending = true;
return {
state,
eventAttendees: [
state.eventAttendees,
newAttendee
],
loading: false,
};
}
case actions.TOGGLE_ATTENDANCE: {
const updatedEventAttendees = state.eventAttendees.map(item => {
if(item.id === action.payload.id) {
item.attending = action.payload.attending;
}
return item;
});
return {
state,
eventAttendees: updatedEventAttendees,
loading: false,
}
}
case actions.DELETE_ATTENDEE: {
const updatedEventAttendees = state.eventAttendees.filter(item => item.id !== action.payload);
return {
state,
eventAttendees: updatedEventAttendees,
loading: false,
}
}
default:
return state;
}
};
export default eventReducer;
This should look very familiar as it's virtually identical to the reducer function from lesson 1. There are a couple of subtle differences here, mainly where we’re referencing the action.payload
value directly, rather than extracting it via destructuring into separate variables.
That aside, we still have to create a copy of state
with our updates applied and return it from our matching cases. Everything else should look about the same though and it’s a pattern that you’ll start to see again and again when you get more adept with redux - this idea of adding items to arrays, changing values in a particular item in an array, and removing items from an existing array. You’ll see heavy and frequent use of both the map()
and filter()
functions as they both return a copy of an existing array which is perfect for us as we don’t want to affect the original state
.
This lesson preview is part of the Beginner's Guide to Real World React course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Beginner's Guide to Real World React, plus 70+ \newline books, guides and courses with the \newline Pro subscription.
