State Management on React [Part 2] - Redux and Redux Toolkit
Kevin Toshihiro Uehara
Posted on May 31, 2023
Hi people! How are you doing? Are you ok?
I hope you guys liked the previous article where I talk about state management using the Context API. Now we will create the same application, but using Redux, more specifically using React Toolkit (this library, will make the implementation simpler and faster).
We are going to create a simple application but we will be able to see the code of the technologies and compare the implementation.
I'll cover these technologies in each article:
Summary:
Introduction
Probably Redux it is the oldest library for state management on React. I remember to use in the early days of React, when we could only control the state on class components and passing the state via prop drilling.
When the state became more complex and we need to share between components we used Redux.
But it was a new and verbose tool, where we can share the state between components. Sometimes the application managament state became complex to maintain in some cases.
Buuuuut, like any tool over time it evolves and develops, becoming easier.
But before I start the development of our application, let's rember how Redux works...
Redux is a library created and inspired on the Flux architeture created by facebook.
You can use the Redux Dev Tools chrome extension to see the actions and state.
We need to know that Redux is formed by three main steps or flows:
- Actions
- Reducers
- Store
The main flow using Redux happens like the example above.
First the View will dispatch an Action. The action is some interaction of the user with the page. For example, click on button to change the theme color of the page. We will dispatch an event called
CHANGE_THEME
.Now the reducer, will receive this action, will interpret and make something with this action. The reducer always will get the old state and the action. We can add some business logic on our reducer and we will replace the old state, with the new state on store. (Immutability)
Store is where the states of our application are "stored". After the reducer change the old state to the new state on Store, all the components that are listening (subscribed) this state, it will update.
This is a summary of how redux works.
Today the most of web applications that use redux, use structure of directorys to map the Actions and create a Reducer for each feature which will need a state. Using two dependencies redux and react-redux
. So we need to create some files to manage our actions, reducers and stores.
And nowadays, a library emerged to facilitate the implementation of redux, called redux-toolkit
. We will be using the library to manage our state in the app. Basically, this library change the way we create the actions and reducers in a sigle file. we usually call slice, but you can call the way of you want.
Just to remember again, if you did not saw the first part, I created all components that we will use in this and next parts on the first part. Soooo if you did not saw yet, I recommend to see on: State Management on React [Part 1] - Context API.
We will use the first version of the components that we created without the Context API integration.
Now with all concepts at hands, LET'S TO THE CODE!
Show me the code
Let's add the dependencies that we will use:
yarn add redux react-redux @reduxjs/toolkit
I will create a directory on /src
called redux
and for each feature I will create a folder with the name of the feature. So, we have two features in our app: change theme color (dark mode) and a todo list:
I will start with the dark mode feature. We will create a file on theme/slice.ts
and create this code:
import { createSlice } from "@reduxjs/toolkit";
export interface ThemeState {
isDark: boolean;
}
const initialState: ThemeState = {
isDark: false,
};
export const slice = createSlice({
name: "theme",
initialState,
reducers: {
changeTheme: (state) => {
return { ...state, isDark: !state.isDark };
},
},
});
export const { changeTheme } = slice.actions;
export const getTheme = (state: { theme: ThemeState }) => state.theme.isDark;
export default slice.reducer;
This single file it is creating the actions and reducers, using the createSlice
function. So in this function we need to provide the name theme
, provide an initial state and on reducers we will provide the object containing the functions that will update our store.
Finnaly I export the actions of slice.actions
and export default the reducers using slice.reducers
.
To get the theme easily I created the function to get the state called 'getTheme'.
Now for the Todos feature, let's create on redux/todo
two files. First the types types.ts
(my preference) and the slice.ts
.
The types.ts
contains just the type of our todo:
export interface ITodo {
id: number;
label: string;
done: boolean;
}
And on the todo/slice.ts
let's add this code:
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { ITodo } from "./types";
const initialState: ITodo[] = [];
export const todoSlice = createSlice({
name: "todo",
initialState: {
todos: initialState,
},
reducers: {
addTodo: (state, action: PayloadAction<string>) => {
const newTodo: ITodo = {
id: Math.random() * 1000,
done: false,
label: action.payload,
};
return { ...state, todos: [...state.todos, newTodo] };
},
removeTodo: (state, action: PayloadAction<number>) => {
const removed = state.todos.filter((todo) => todo.id !== action.payload);
return { ...state, todos: removed };
},
},
});
export const { addTodo, removeTodo } = todoSlice.actions;
export const getTodos = (state: { todo: { todos: ITodo[] } }) =>
state.todo.todos;
export default todoSlice.reducer;
The same exactly idea we applied here. Now we have two functions to add and remove todo item. Remember the concept of immutability, so we always get the old state the increment using spread operator to update the state.
Now we need to provide the slices as a store. So we have two slicers and we need to connect to provide as state store for o our application.
We will create a single file on redux/store.ts
to combine all slicers and configure the store. So on the store.ts
:
import { configureStore } from "@reduxjs/toolkit";
import themeReducer from "./theme/slice";
import todoReducer from "./todo/slice";
export default configureStore({
reducer: {
theme: themeReducer,
todo: todoReducer,
},
});
We will combine the reducers. If you want to create another slicer, just add on this object to get mapped.
And we are done! All states are created and now let's change the components to use this states and dispatch the actions.
Let's change the ButtonChangeTheme
adding this simple code:
components/ButtonChangeTheme/index.tsx
import { useDispatch } from "react-redux";
import button from "./button.module.css";
import { changeTheme } from "../../redux/theme/slice";
interface ButtonChangeThemeProps {
label: string;
}
export const ButtonChangeTheme = ({ label }: ButtonChangeThemeProps) => {
const dispatch = useDispatch();
return (
<button className={button.btn} onClick={() => dispatch(changeTheme())}>
{label}
</button>
);
};
The useDispatch
is from react-redux
, so we call the action to dispatch the event. And the changeTheme
it is working as a action for the redux. AMAZING, isn’t it?
Now on our Content
dummy component, let's consume the state using the useSelector
of react-redux
:
components/Content/index.tsx
import { useSelector } from "react-redux";
import { getTheme } from "../../redux/theme/slice";
interface ContentProps {
text: string;
}
export const Content = ({ text }: ContentProps) => {
const isDark = useSelector(getTheme);
return (
<div
style={{
height: "30vh",
width: "100vw",
color: isDark ? "#fff" : "#111827",
backgroundColor: isDark ? "#111827" : "#fff",
}}
>
{text}
</div>
);
};
Now on our FormTodo
component, let's dispatch the action to add a todo item:
components/FormTodo/index.tsx
import style from "./Form.module.css";
import { Button } from "../Button";
import { Input } from "../Input";
import { useState } from "react";
import { useDispatch } from "react-redux";
import { addTodo } from "../../redux/todo/slice";
export const FormTodo = () => {
const [todo, setTodo] = useState("");
const dispatch = useDispatch();
const handleAddTodo = () => {
dispatch(addTodo(todo));
setTodo("");
};
return (
<div className={style.formContainer}>
<Input
value={todo}
label="Todo"
onChange={(evt) => setTodo(evt.target.value)}
/>
<Button label="Add" onClick={handleAddTodo} />
</div>
);
};
And finnaly, let's change the ListTodo
component to dispatch the action to remove the todo item and consume the list of todos using the selector:
components/ListTodo/index.tsx
import { useDispatch, useSelector } from "react-redux";
import style from "./ListTodo.module.css";
import { getTodos, removeTodo } from "../../redux/todo/slice";
export const ListTodo = () => {
const dispatch = useDispatch();
const todos = useSelector(getTodos);
const handleRemoveTodo = (id: number) => {
dispatch(removeTodo(id));
};
return (
<ul>
{todos.map((todo) => (
<li className={style.item} key={todo.id}>
<label>{todo.label}</label>
<i
className={style.removeIcon}
onClick={() => handleRemoveTodo(todo.id)}
/>
</li>
))}
</ul>
);
};
EASY PEASY
To finish, lets add the Provider
in our app tree on main.tsx
, passing the store:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { Provider } from "react-redux";
import store from "./redux/store.ts";
import { ButtonChangeTheme } from "./components/ButtonChangeTheme/index.tsx";
import { Content } from "./components/Content/index.tsx";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<Provider store={store}>
<ButtonChangeTheme label="Change Theme" />
<Content text="Hello World!" />
<App />
</Provider>
</React.StrictMode>
);
And now we finished the application using redux as global state management!
Results
If you remember, on first part of this article of Context API. Some problems of it was the re-render because of when we update the state, all the childrens will be rendered again.
But with redux, using the selector, we can manage the state and update just only the components that use that state.
Let's see on React Dev Tools:
First using highlight of render of component:
We can see that when we change the theme, the form is not affected by re-render by highlight.
Now using the profiler viewer record:
And additionally you can use the Redux Dev Tools to see the actions dispatched and the state:
We see that the both the change theme and remove todo just affeect only the components that use THAT state. And this is amazing, because we are talking about of fixing a problem of a simple way, without having to create out-of-the-box solutions like useMemo or Memo.
Conclusion
I'm not saying that redux is the best library and that you should use it in your project. So much so that the purpose of these articles is to compare the application and implementation of each state manager.
But it's a tool that I am presenting to you in a superficial way. If you are interested, look for the documentation to go deeper into the concept of the tool.
So in summary, in this article we saw how we can implement the redux, using the new library react-toolkit
to manage our states, creating slicers and combine on our store.
In the next part, I will talk about (spoilers) another state manager (for now what I've been most enjoying using), called JOTAI. But for now, that's it!
Some references:
That's all folks!
I hope you enjoyed it and added some knowledge. See you in the next parts!
Posted on May 31, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.