Redux is one of the most well-known state management libraries, which can seem difficult for beginners to learn because it requires a lot of boilerplate code. However, we should understand that it is used by many companies out there, and they are still enjoying it. That said, it does not mean Redux is the best fit for every state management problem.
Problem
Let's take a look at this app. We have three components: the App component, the Login component, and the Profile component.
The Login component handles the login logic and renders the login form (UI). However, we want to access the same user on the profile page.
- The first solution could be passing the props from Login to Profile, but in React, we cannot pass props directly between two sibling components. 😔
- The second solution is to write the login logic in the App component so that from App, we can pass the props to both Profile and Login if needed. 🤩
Hold on — we can't be happy yet! If we want to build an app that can handle more than 100 states across different components, the second solution won't scale well. The code will quickly become unmaintainable.
What if we had a store somewhere, which could save all our states, and whenever we needed to access or modify a state, we could simply interact with that store?
Well, that’s where Redux comes into play.
Redux installation
Redux is not just for React; it is a JavaScript library for state management that can be used with other JavaScript frameworks and libraries as well.
First, we need to install the Redux library itself.
To connect Redux to React, we need another library called react-redux.
Finally, since Redux has improved and now provides a toolkit that makes it easier to use, we will also install the Redux Toolkit.
With the command below, we can install everything we need to start working with Redux in React:
npm install redux react-redux @reduxjs/toolkit
Once all the libraries are installed, let's start using Redux.
Set up the Redux store
As mentioned earlier, we need a store to hold all of our application states.
With Redux Toolkit, in the index.js
file, we can import the configureStore
function, which will contain all reducers in the app.
import { configureStore } from "@reduxjs/toolkit";
For now, since we don’t have any reducers created yet, we will configure the reducer as an empty object.
Let’s create a variable called store
that holds our configureStore
function and all reducers.
const store = configureStore({
reducer: {},
});
Set up the Provider
We have configured our store — now we need to make it accessible throughout the app.
That’s where react-redux
comes in. 🤠
It provides a Provider
component that will wrap the App
component and shares the store across all child components. The Provider
component requires a store
prop.
In the same index.js
file, let’s import the Provider
from react-redux
and pass our store to it:
import { configureStore } from "@reduxjs/toolkit";
import { Provider } from "react-redux";
const store = configureStore({
reducer: {},
});
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
Now the store is ready to be accessed. Next, we are going to set up our first reducer.
Set up a reducer
A reducer is just a function that takes the previous state and an action, and returns the updated state.
In the /src/components
folder, let’s create a new folder called features, which will contain all our reducers.
This folder is named features because it's the recommended standard when using Redux Toolkit.
Let’s create a user reducer step by step. This reducer will hold the user's name, age, and email.
- Import the
createSlice
function from Redux Toolkit. Think of a slice like a slice of pizza 🍕 — it’s a small part of the whole pizza, but it contains all the ingredients. Similarly, a slice contains the slice name, the initial value, and the reducer itself in one place. Isn’t that better? 🤗
import { createSlice } from "@reduxjs/toolkit";
export const userSlice = createSlice({
name: "user",
initialState: {
value: {
name: "",
age: 0,
email: "",
},
},
});
- Add the reducer function to the slice.
After setting the initial value, we need to add a function that can update the value. This function takes two parameters —
state
(the previous state) andaction
. Theaction
object has two properties:
-
payload
(the information that updates the state) -
type
(used for distinguishing actions — though we won’t focus on it here)
export const userSlice = createSlice({
name: "user",
initialState: {
value: {
name: "",
age: 0,
email: "",
},
},
reducers: {
login: (state, action) => {
state.value = action.payload;
},
},
});
export default userSlice.reducer;
The above code is the full userSlice
. It includes a reducers
object — in our case, the login
reducer. Every time we dispatch the login
action, it will update the state value with the passed payload.
- Back in
index.js
, let’s now add our reducer to thereducer
object and call ituser
:
import { configureStore } from "@reduxjs/toolkit";
import { Provider } from "react-redux";
import userReducer from "./components/features/user";
const store = configureStore({
reducer: {
user: userReducer,
},
});
Access and modify the state
We have done everything needed — from creating the store to setting up a reducer — but we need to access and modify the data. That’s the whole point!
1. Access a state
To access a state, we use a hook provided by the react-redux
library called useSelector.
It is used to grab state values. This hook takes a callback that returns a value from our state and accepts an argument, commonly called state
.
To return the value, we access the reducer's value by using the reducer name followed by .value
.
Note: The reducer’s name is different from the slice name. The reducer name is the property that holds the reducer in the store (in our case, in
configureStore
in **index.js) — i.e.,user
.
const store = configureStore({
reducer: {
// This is the reducer name used to access its value.
user: userReducer,
},
});
Let’s create a component called Profile
in the /components path, and implement the useSelector hook to return the user's value, and render it.
import React from "react";
import { useSelector } from "react-redux";
export default function Profile() {
const user = useSelector((state) => state.user.value);
return (
<div>
<h1>Profile page</h1>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<p>Age: {user.age}</p>
</div>
);
}
2. Modify a state
Just as we accessed the state with useSelector, to modify it we use another hook called useDispatch (also provided by react-redux
).
The dispatch function is used to send actions to the store.
Let’s create a simple component called Login, where clicking a button updates the state in the store.
First, go back to /components/features/user.js (which contains our slice) and export the login
action:
export const { login } = userSlice.actions;
Then in components/Login.js, let’s implement the dispatch logic:
import React from "react";
import { useDispatch } from "react-redux";
import { login } from "./features/user";
export default function Login() {
const dispatch = useDispatch();
return (
<div>
<button
onClick={() =>
dispatch(
login({
name: "Jonathan",
email: "jonathan@gmail.com",
age: 20,
})
)
}
>
Login
</button>
</div>
);
}
We first initialized the hook in the component. Then, on the button click event, we called the dispatch
function with the action to perform.
So what is that object in the login
? 🤔
Good question.
To modify the state, we need to provide a payload — i.e., the data that will update the state.
In this case, our payload is the object with the name, email, and age.
Conclusion
Thank you for reading this article.
Now that you know the basics of using Redux and the Redux Toolkit in React, you can continue exploring these capabilities — and you won’t be disappointed.
Feel free to contact me if you have any questions:
Top comments (0)