Managing state is an important aspect of React, For a smaller project, using useState or useReducer is a good way to manage state. But as the application grows complex, we need an efficient way to handle it, React-Redux provides us with global state management and in this article we will explore the concept of state management using Redux-ToolKit (RTK).
Table of Content
Introduction
When an action is performed,a state change is expected to happen and trigger the rendering of UI. Managing state can be done using a useState or useReducer, It could be complex especially if those components are located in different parts of the application. Sometimes this can be solved by "lifting state up" to parent components, but that doesn't always help.
One way to solve this is to extract the shared state from the components, and put it into a centralized location outside the component tree where any component can access the stateor trigger actions, no matter where they are in the tree. This is the first of three articles. Other articles are
In those articles, I will explain how to use RTK queries to make API calls in efficient ways rather than installing other packages, I will also explain redux-persist as an alternative to saving to persist our state or save with details in our App. see you soon.
What is React-redux
React-Redux is a popular JavaScript library that helps developers manage the state of their applications. React-Redux provides a way for a React application to communicate with a central store that holds the state of the application. If you are familiar with react-redux, there are several challenges using it as explained by the documentation ranging from
1. Configuring a Redux store is too complicated
2. Installing many Packages
3. Too much boilerplate code
With those challenges, RTK is meant to solve them and provide a way to write efficient ways to set up and store and define reducers,
Installation
For an existing project, run the following code in your terminal
npm install @reduxjs/toolkit react-redux
OR
yarn add @reduxjs/toolkit
For a new project and this tutorial, we will create an App with redux template, run the code below, our App name is redux-tutorial
npx create-react-app redux-tutorial --template redux
After the installation, run
cd redux-tutorial
code .
npm start
Your package.json file should like this, redux-toolkit and react-redux will be installed.
Setting up
In this Tutorial, we will be building a simple Todo list to illustrate our work. We need to basically go through three basic step as listed below
- Configure Store
- Wrap our App with Provider from react-redux *
- Create Slices
- Dispatch our action and get state
*Only necessary if you are using an existing App
In our features folder, a subfolder of src, create another folder called todo where all our configurations will be. For those that installed with redux template, all configurations have been done. For existing project, create a folder store and add index.js.
ConfigureStore
The configureStore function is used to create a Redux store that holds the complete state tree of our App. Configure store accepts reducers as the required parameters, others parameters that can be included are
- Thunk middleware
- Redux DevTools
- PreloadedState
- Enhancers
Lets create our storeby importing the following
src/app/store.js OR ./store
import { configureStore } from '@reduxjs/toolkit';
import todoReducer from "features/todo/todoSlice.js"
export const store = configureStore({
reducer: {
todos: todoReducer,
},
});
Here, we import our todoReducer (which will be created soon). the import was done using absolute path. Read more about absolute path in my article here. We can have multiple reducers combined together. later on, we will use todos as the key to access our state.
Wrap our App with Provider from react-redux
import React from "react";
import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import { store } from "./app/store";
import App from "./App";
const container = document.getElementById("root");
const root = createRoot(container);
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
CreateSlice
This helps us to write reducers without the need to spread our state. Redux Toolkit allows us to write "mutating" logic in reducers. It doesn't actually mutate the state because it uses the Immer library, It also automatically generates action creator functions for each reducer. It literarily saves us with writing our actions type logic repetitively.
In features folder, create another folder todo and add todoSlice.js where we write our todoSlice
todoSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
todoList: [],
};
export const todoSlice = createSlice({
name: "todos",
initialState,
reducers: {},
});
const { actions, reducer } = todoSlice;
export const {} = actions;
export default reducer;
This is a boilerplate for writing our slices, in our createSlice function, we passed in our name, the initial State and our reducers.
Actions
let's write our first action, addTodo add a new todo to the list and we create it in reducers object as below
Add Todo
addTodo: (state, action) => {
state.todoList.push(action.payload);
}
Here we are using the push method to add new todo to our list. action.payload is the todo that will be added to todoList.
Delete Todo
To be able to delete from the list, we use the index to select a particular todo and use the array method of splice. here is the code:
deleteTodo: (state, action) => {
state.todos.splice(state.todos.indexOf(action.payload), 1);
},
Update Todo
updateTodo: (state, action) => {
const todo = state.todoList.findIndex(action.payload.id);
state.todos[todo] = action.payload;
}
We can export all our actions and our final code like this
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
todoList: [],
};
export const todoSlice = createSlice({
name: "todos",
initialState,
reducers: {
addTodo: (state, action) => {
state.todoList.push(action.payload);
},
deleteTodo: (state, action) => {
state.todoList.splice(state.todoList.indexOf(action.payload), 1);
},
updateTodo: (state, action) => {
const todo = state.todoList.findIndex(action.payload.id);
state.todoList[todo] = action.payload;
},
},
});
const { actions, reducer } = todoSlice;
export const { addTodo } = actions;
export default reducer;
With this we concluded writing on redux logic, last thing we need to do is to import it in our App.
useDispatch and useSelector
The two hooks work to get set and get data from our store. if we need to modify the state, useDispatch will be used to dispatch an action while if we need to access our state, useSelector will be used.
Todo App
For this, we created our Todo component in the root of our src folder, our folder structure should look like this
We create a simple UI and you can have access to the code in the linked repository.. Here is the code in our Todo.js file
import { useState } from "react";
import { useSelector } from "react-redux";
const Todo = () => {
const [todo, setTodo] = useState("");
const state = useSelector((state) => state.todos.todoList);
return (
<div className="todo">
<h3>Simple Todo App</h3>
<div className="input__container">
<input
type="text"
value={todo}
onChange={(e) => setTodo(e.target.value)}
/>
<button>Add Todo</button>
</div>
<ol>
{state.length > 0 &&
state.map((todo, index) => {
return (
<li key={index}>
<h6>{todo} </h6>
<button name="edit">Edit</button>
<button name="delete">
Delete
</button>
</li>
);
})}
</ol>
</div>
);
};
export default Todo;
The first thing we do here is to get the current state of our todoList using useSelector hook, we have
Get Initial State
const state = useSelector((state) => state.todos.todoList);
todos here refer to the name of the reducer we used when configuring our store. So we have access to our todoList array which contains our initialState and if we log it to console, we have
Add a Todo
To add to the list, we need to dispatch our addTodo action as shown below. we will use useDispatch hook.
- first we call the useDispatch and assigned it to dispatch variable.
const dispatch = useDispatch();
- We dispatch our action when we clicked the button, with our
todoas a paramater as shown below. we checked if there'stodobefore we dispatch our action and clear our input field afterwards
<button onClick={() => {
if (!input) return;
dispatch(addTodo(input));
setInput("");
}}
>
Add Todo
</button>
on the click of Add Todo button, we have something similar to this
Delete a todo
Similar to what we did for addTodo, we will pass the index a todo and dispatch our deleteTodo action
import { addTodo, deleteTodo } from "features/todo/todoSlice";
In our edit button, we add the code to set our todo to the input value, here is how our button code looks like
<button name="delete" onClick={()=>dispatch(deleteTodo(index))}>
Delete
</button>
Update a todo
Here , we need to select a particular todo item and make update to it, full details to the implementation is available in the repo.
Thank you for reading. Please leave a comment if you have any question or clarification and I hope you've learned something new from this post. Want to stay up to date with regular content regarding JavaScript, React and React-Native? Follow me ❤️ on LinkedIn.




Top comments (0)