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 state
or 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 store
by 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
todo
as a paramater as shown below. we checked if there'stodo
before 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)