Table of contents
- Prerequisites
- What is Redux Toolkit?
- Why Redux Toolkit over Redux?
- Setting up Redux Toolkit in a React app
- Create Redux Toolkit Store
- Connect Store to React
- Slices
- Create a TodoSlice
- Connect the todoSlice to the Redux store
- Creating a Todo component
- Connect the Todo component to the App
- Test the application
- Benefits of State Management
- Conclusion
- Resources and References
State management plays a crucial role in application development by maintaining knowledge of an application's inputs across multiple related data flows. In the realm of React, effectively managing the state becomes even more essential. React state management involves handling the data that React components require to render themselves accurately.
When dealing with data flows without proper state management, components may re-render unnecessarily, leading to performance issues. Additionally, passing props through multiple parent elements to reach child components—known as "props drilling"—can become cumbersome and hinder code maintainability. This is where Redux Toolkit comes into the picture, offering a solution to eliminate props drilling and manage complex data flows seamlessly.
In this article, we will delve into the powerful capabilities of the Redux Toolkit and explore how it enables efficient state management in React applications.
We will build a simple Todo
app at the end of this article.
Prerequisites
To better follow this article, you should have
a basic understanding of React and JavaScript
a web browser (preferably Chrome)
Node.js installed on your computer
Package manager (Yarn or NPM)
A text editor (preferably VS Code)
A cup of water or coffee
What is Redux Toolkit?
According to the Redux Toolkit documentation: "Redux Toolkit is an opinionated, official toolset for Redux, a popular state management library in JavaScript. It provides a set of utilities and abstractions that simplify the process of working with Redux, making it easier and more efficient to manage the application state".
Before Redux Toolkit, developers were dependent on Redux to manage the state, which can be tedious to configure the store, and requires lots of boilerplate codes.
Why Redux Toolkit over Redux?
Redux functions are still pure javascript functions, that are dependent on switch statements, hand-written action creators, and action types using spread operator to manually update the store. The redux core provides these APIscreateStore
, combineReducers
, applyMiddleware
, and compose
to manage the state, which is limited to managing complex states, leading to more external libraries.
A practical todo code in Redux:
const ADD_TODO = 'ADD_TODO'
const TODO_TOGGLED = 'TODO_TOGGLED'
export const addTodo = text => (
{ type: ADD_TODO,
payload: { text, id: nanoid() }
})
export const todoToggled = id => (
{ type: TODO_TOGGLED,
payload: id
}
)
export const todosReducer = (state = [], action) => {
switch (action.type) {
case ADD_TODO:
return state.concat({ id: action.payload.id,
text: action.payload.text,
completed: false
})
case TODO_TOGGLED:
return state.map(todo => {
if (todo.id !== action.payload.id)
return todo
return { ...todo, completed: !todo.completed } })
default: return state
} }
In the context of Redux Toolkit, all this code is shorter and simplified using Redux Toolkit core APIs which are:
configureStore
sets up a well-configured Redux store with a single function call, including combining reducers, adding the thunk middleware, and setting up the Redux DevTools integration. It also is easier to configure, because it takes named options parameters.createSlice
lets you write reducers that use the Immer library to enable writing immutable updates using "mutating", JS syntax-likestate.value = 123
, with no spreads needed. It also automatically generates action creator functions for each reducer, and generates action type strings internally based on your reducer's names.
The same todo code in Redux Toolkit:
import { createSlice } from '@reduxjs/toolkit'
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo(state, action) {
state.push({
id: action.payload.id,
text: action.payload.text,
completed: false
})
},
todoToggled(state, action) {
const todo = state.find(todo => todo.id === action.payload)
todo.completed = !todo.completed
}
}
})
export const { addTodo, todoToggled } = todosSlice.actions
export default todosSlice.reducer
All of the action creators and action types are generated automatically, and the reducer code is shorter and easier to understand.
Setting up Redux Toolkit in a React app
Now that you have known why many developers are adopting Redux Toolkit, let's install it in our React app and set up our store.
We will be using Vite to bootstrap the React app, which is fast and lightweight.
Go here to learn more about it 👉 https://vitejs.dev/guide/.
I will be using Visual Studio Code (vs-code), and its built-in terminal in this practical, feel free to use any terminal, also with the yarn package manager, read about it
here 👉 https://yarnpkg.com/.
Install Vite React app:yarn create vite
Complete the vite prompts, with these values:
Project name: todo-react
Select a framework: › React
Select a variant: › JavaScript
Nextcd into todo-react
install react by typing yarn
and hit enter.
Then install the Redux Toolkit, Redux, and React-Redux, which binds the Redux to React
yarn add redux react-redux @reduxjs/toolkit
We are ready to look at our project and configure our store.
Create Redux Toolkit Store
Open the vs-code terminal, then start the project locally by typing dev
and hit enter, you should have something like this running
Next, create a folder in your root src directory called redux,
then create a file called store.js.
To configure a store, we need to leverage the configureStore
function from the Redux toolkit. Which accepts a single configuration object parameter, with the following options:
reducer
(required): This property specifies the root reducer for your Redux store. It can be a single reducer function or a combination of reducers created using Redux'scombineReducers
utility.middleware
: You can provide an array of middleware functions that intercept actions before they reach the reducers. Middleware can be used for tasks such as logging, async handling, or modifying actions. Redux Toolkit includes a default middleware called "redux-thunk" for handling async actions.preloadedState
: If you want to initialize the state of your store with some preloaded values, you can provide them as thepreloadedState
property.devTools
: By default, Redux Toolkit automatically configures the Redux DevTools extension for development mode. If you want to disable or customize the DevTools integration, you can set thedevTools
property tofalse
or provide a custom configuration object.
These parameters reducer
, devTools
, middleware
are required to properly set up the store.
// store.js
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({
reducer: {
// todos: // todosReducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(),
devTools: true,
});
export default store;
Connect Store to React
To connect our store to our React app, we will import the Provider
component from the React-redux library we installed earlier. Then wrap our app with this Provider,
passing our store as a prop.
The Provider
component serves as the root component that allows React components to access the Redux store and its state. The Provider
component ensures that any nested components can access the store without explicitly passing it as a prop through each component level.
// main.js
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import App from "./App.jsx";
import "./index.css";
import store from "./redux/store.js";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
Slices
Redux Toolkit comes with a function called createSlice
, it simplifies the process of creating Redux reducers and action creators. It combines several Redux-related concepts into a concise and declarative syntax, reducing boilerplate code.
Create a TodoSlice
Inside our redux folder, create a todoSlice.js file, this file will contain a single slice function, which we will write our action creators code, to add a Todo, delete a Todo, and mark it as completed.
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
todos: [],
};
const todoSlice = createSlice({
name: "todos",
initialState,
reducers: {
addTodo: (state, action) => {
state.todos.push(action.payload);
},
toggleTodo: (state, action) => {
const todo = state.todos.find((todo) => todo.id === action.payload
);
if (todo) {
todo.completed = !todo.completed;
}
},
deleteTodo: (state, action) => {
const index = state.todos.findIndex((todo) => todo.id === action.payload);
if (index !== -1) {
state.todos.splice(index, 1);
}
},
},
});
export const { addTodo, toggleTodo, deleteTodo } = todoSlice.actions;
export default todoSlice.reducer;
In the code snippet above, we import createSlice
from Redux Toolkit to simplify the process of creating a Redux slice. We define our initial state for the todo. Then, using createSlice
, we create a slice with a name, initial state, and a reducer object. The reducer object contains functions that are used to update the application state. Finally, we export these functions and the reducer to make the slice accessible in our React app.
Connect the todoSlice to the Redux store
To connect our slice to our store, open the store.js
file inside the redux folder, and paste the code snippet below.
import { configureStore } from "@reduxjs/toolkit";
import todosReducer from "./TodoSlice";
const store = configureStore({
reducer: {
todos: todosReducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(),
devTools: true,
});
export default store;
In the next section, we will create a to-do component.
Creating a Todo component
Navigate to your src folder and create a new folder called "components", then create a new file called "Todo.js" inside the components folder. In the Todo.js folder add the following code:
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { addTodo, deleteTodo, toggleTodo } from "./redux/TodoSlice";
import "./todo.css";
const Todo = () => {
const [todoText, setTodoText] = useState("");
const dispatch = useDispatch();
const { todos } = useSelector((state) => state.todos);
const handleAddTodo = () => {
if (todoText.trim() !== "") {
dispatch(
addTodo({
id: Date.now(),
text: todoText,
completed: false,
})
);
setTodoText("");
}
};
const handleToggleTodo = (id) => {
dispatch(toggleTodo(id));
};
const handleDeleteTodo = (id) => {
dispatch(deleteTodo(id));
};
return (
<div className="todo-container">
<h1>Todos</h1>
<div className="add-todo">
<input
type="text"
placeholder="Enter a todo"
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
/>
<button onClick={handleAddTodo}>Add Todo</button>
</div>
<ul className="todo-list">
{todos.map((todo) => (
<li
key={todo.id}
className={`todo-item ${todo.completed ? "completed" : ""}`}
>
<h3 className="todo-text" onClick={() => handleToggleTodo(todo.id)}>
{todo.text}
</h3>
<div>
<button
className={`todoToggle ${
todo.completed ? "incomplete" : "complete"
}`}
onClick={() => handleToggleTodo(todo.id)}
>
{todo.completed ? "Undo" : "Complete"}
</button>
<button
className="deleteTodo"
onClick={() => handleDeleteTodo(todo.id)}
>
Delete
</button>
</div>
</li>
))}
</ul>
</div>
);
};
export default Todo;
In the above code snippet, we imported two functions from react-redux
called useSelector
and useDispatch
. Additionally, we imported our action creators functions from our slice file. Inside our Todo component, we define a function hook from useDispatch
, then use the Javascript object destructuring method and destructure our todos array from our store. Additionally, we created three different functions, namely: handleAddTodo, handleToggleTodo, and handleDeleteTodo. The handleAddTodo function dispatches our addTodo action to create new todo and add it to our todos list in our application state, the handleToggleTodo, takes the todo id as an argument, which toggles the given todo from incomplete to complete. Lastly, the deleteTodo function removes the given todo from our application state using its id. Then we return JSX elements with our functions and pass the required parameters, then we export our component.
Connect the Todo component to the App
Open your App.js file inside the src directory, then remove all the Vite boilerplate code and include the Todo component:
import "./App.css";
import Todo from "./Todo";
function App() {
return <Todo />;
}
export default App;
We are ready to test our application and debug any potential bugs.
Test the application
Open the VS-code terminal and restart the development server
yarn dev
Voila, our application is working as expected
Benefits of State Management
State management in applications offers several benefits, including:
Centralized Data: State management allows you to centralize and organize your application's data in a single location, making it easier to access and manage. This eliminates the need for scattered data across various components.
Data Consistency: With state management, you can ensure data consistency throughout your application. Any changes made to the general state are reflected across all components that rely on it, reducing the risk of inconsistencies.
State Persistence: State management solutions often provide mechanisms for state persistence, allowing you to save and restore the application state across sessions or page refreshes. This can enhance the user experience by retaining important data and maintaining the application's state between interactions.
Enhanced Performance: By efficiently managing the state and updating only the necessary components when changes occur, state management can optimize performance. It helps prevent unnecessary re-rendering of components, resulting in smoother user interfaces and improved application speed.
Conclusion
In this tutorial, we have learned how to add state management to React App using Redux Toolkit. We have also learned how to create a to-do task, and dispatch actions from our Redux Toolkit createSlice function.
You can check out the full code for this tutorial Here
Resources and References
You can check out some of the resources listed below to learn more about Redux Toolkit