How to Implement Redux Reducers for React
If you plan to use Redux as a state management tool, you’ll have to deal with Flux concepts like reducers and actions. In this article, we will discuss what they are and how to better implement them for your React apps.
What is State?
The state is a type of object that specifies characteristics of your program that can change over time. You should make your state the bare minimum of data required to explain the application’s current state. If Redux manages an application’s state, state updates are handled by a reducer that processes actions, as we’ll see below. For example, our chat application could be in the following state:
{
userId: "user-2022",
username: "Great",
messages: [
{
user: "Great",
message: "Hi",
},
{
user: "Hannah",
message: "Hello there!",
},
],
};
What are Actions?
Actions are a simple JavaScript object that holds data. Actions have a type field that specifies the type of action to take, and all other fields contain data or information. Actions are the sole way for the Redux store to be updated with new information; they provide the payload for changes to an application store. Actions tell Redux what kind of action to perform, like the following:
const action = {
type: "ADD_TO_CART",
payload: {
product: "doughnut",
quantity: 6,
},
};
The code above is a typical payload
value that contains the data that a user sends and is used to change the application’s state. As you can see from the example above, the action object contains the type of action and a payload object required for this action to be completed. Reducers update the store based on the action.type
value; here, ADD_TO_CART
.
What is a Reducer?
A reducer is a pure function in Redux that accepts an action and the application’s previous state and returns the new state. The action specifies what occurred, and the reducer’s role is to return the updated state as a result of that action. Pure functions have no side effects and will produce identical outcomes if the same arguments are handed in.
An example of a pure function is as follows:
const add = (a, b) => a + b;
add(3, 6);
The code above returns a value based on the inputs; for example, if you pass 3
and 6
, you’ll always get 9
. As long as the inputs are the same, nothing else influences the result; this is an example of a pure function.
A reducer function that takes in a state and action is shown below:
const initialState = {};
const cartReducer = (state = initialState, action) => {
// Do something here
};
Each time an action is dispatched, the state is updated in terms of its current value and the incoming values in the action.
Updating State Using Reducers
Let’s have a look at the number counter below to see how reducers work:
const increaseAction = {
type: "INCREASE",
};
const decreaseAction = {
type: "DECREASE",
};
const countReducer = (state = 0, action) => {
switch (action.type) {
case "INCREASE":
return state + 1;
case "DECREASE":
return state - 1;
default:
return state;
}
};
Here, increaseAction
and decreaseAction
are actions that determine how the state
is updated. Next, we have countReducer
, a reducer function that takes an operation and a zero-valued initial state. We return a new state that is increased by one if the value of action.type
is INCREASE
, and a new state that is decremented by 1 if the value is DECREASE
. We return state
in circumstances where none of those conditions apply. The initial state is zero.
Updating State Using Reducers: The Spread Operator
State can’t be directly altered. We can use the JavaScript spread operator to ensure that we don’t modify the value of the state directly but instead return a new object with the state supplied to it plus the user’s payload.
const contactAction = {
type: "GET_CONTACT",
payload: ["08123456789", "0901234567"],
};
const initialState = {
contacts: [],
contact: {},
};
export default function (state = initialState, action) {
switch (action.type) {
case GET_CONTACTS:
return {
...state,
contacts: action.payload,
};
default:
return state;
}
}
We use a spread operator in the code above to avoid changing the state value directly. This allows us to return a new object that is filled with the state that is supplied to it and the payload that the user sends. We can ensure that the state remains the same when adding all new items and changing the contacts field in the state (if previously present) by using a spread operator.
Open Source Session Replay
OpenReplay is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data.
Start enjoying your debugging experience - start using OpenReplay for free.
Implement Reducers in our Github-Finder app
We’ll be creating a Github-Finder app with Redux Reducers for a better understanding. We’ll be working on our React app, and we’ll be implementing a Reducers function. We’ll be skipping the building of our app to save time, but you can find it at my GitHub repo. We’ve created our app, and its purpose is to get data from every GitHub user and their repositories. For us to get the USERS
result when we search their names; that’s where our Reducers come in.
So let’s jump to our application as we want to create a specific file for our GitHub context reducer. In the context github
folder, I’m going to create a new file called GithubReducer.js
, where all of our GitHub-state-related reducers will go. We’ll create a function called `githubReducer“.
//githubReducer.js//
const githubReducer = (state, action) => {
switch (action.type) {
case "GET_USERS":
return {
...state,
users: action.payload,
loading: false,
};
default:
return state;
}
};
export default githubReducer;
We go to our githubContext.js
; we’ll be using our reducers here.
import { createContext, useReducer } from "react";
import githubReducer from "./GithubReducer";
const GithubContext = createContext();
export const GithubProvider = ({ children }) => {
const initialState = {
users: [],
loading: true,
};
const [state, dispatch] = useReducer(githubReducer, initialState);
return (
<GithubContext.Provider
value={{
...state,
dispatch,
}}
>
{children}
</GithubContext.Provider>
);
};
export default GithubContext;
We get a result if we search for a user in our github-finder
app.
In the full code for the app, we also use the Reducer function for GET_USER
, GET_USER_AND_REPOS
, SET_LOADING
, and CLEAR USERS
. For the demo of this app click here.
Conclusion
Reducers are a crucial component of Redux state management because they allow us to construct pure functions to update specified portions of our Redux apps without causing side effects. We’ve covered the fundamentals of Redux reducers, their applications, and the core concept of reducers, state, and actions.
You can learn more about Redux reducers in the documentation. For the demo app click here