We may not have the course you’re looking for. If you enquire or give us a call on 01344 203999 and speak to our training experts, we may still be able to help with your training requirements.
Training Outcomes Within Your Budget!
We ensure quality, budget-alignment, and timely delivery by our expert instructors.
f you're building a web application with complex needs, Redux is a powerful tool which can help you maintain a predictable state and simplify the development process. However, Redux can be a bit intimidating for beginners due to its verbose codes and excessive configuration. This is where Redux Toolkit comes in, with its ability to simplify the daunting process of using Redux.
According to a survey by Statista, 40.6 per cent were using React.js, making it one of the most frameworks for programmers. In this blog, we'll explore what Redux is and how its toolkit can make your life easier when working with Redux. Keep reading and learn how Redux Toolkit effectively streamlines state management in your React applications with our practical examples, tips, and techniques.
Table of Contents
1) What is Redux Toolkit?
2) Why should you Redux Toolkit?
3) What are the Prerequisites for using Redux Toolkit?
4) How to Use Redux Toolkit in your project?
5) Redux Toolkit and the Ducks Pattern
6) Best practices for structuring your Redux Store
7) Conclusion
What is Redux Toolkit?
Redux Toolkit is a state management library launched in 2015 to simplify the development of complex applications using Redux. It is frequently used in full-stack development, where a developer works on the front end and back end simultaneously. This library is commonly used with applications coded in React.js, reducing the necessity to code manually, thus decreasing the overall verbosity and complexity of the program.
Redux Toolkit was essentially an improvement upon Redux, which was already used for state management during the development process. It was originally created by Dan Abramov with the help of Andrew Clark, inspired by similarities between a Flux pattern and a reducing function. However, realising how Redux has made the process of coding much more complex, the organisation behind Redux designed this tool kid, which soon got accepted widely.
Why should you Redux Toolkit?
Redux by itself is a powerful state management library, which makes the development of complex applications possible. However, Redux also comes with its fair share of challenges, such as excessive boilerplate codes, managing reducers and other similar issues, which can make the development process extremely complicated to errors. Using Redux Toolkit simplifies these issues, making it important for developers for the following reasons:
Boilerplate code reduction
Redux Toolkit reduces the amount of manually written code needed to create slices, actions and reducers. Without the help of Toolkit, the task of creating action types and reducer functions can be daunting due to the amount of manual code it requires. The Toolkit simplifies this process, which allows you to focus on the core logic of your application. Additionally, the toolkit allows you to define a reducer and action together, which are normally required to be defined separately without the help of the Toolkit.
Simplifying immutable updates
The term immutability in Redux refers to the stability of the state, as it ensures that state changes are predictable and traceable in nature. Redux Toolkit comes with many utility functions that simplify the process of updating the state. This allows you to simplify the process of creating new state objects while preserving the integrity of the existing state. As a result, you can write code feely while using his Toolkit without worrying about the immutability of the code.
Managing store setup
Setting up and configuring a Redux store from scratch can be a daunting task, especially if you are a beginner. The Redux Toolkit streamlines this process by offering a more intuitive and straightforward way to create and configure your Redux store. This abstraction process simplifies the store setup and reduces the potential for configuration errors, saving you valuable development time.
What are the prerequisites for using Redux Toolkit?
Using Redux Toolkit with your applications and code requires you to integrate it, thus setting up the prerequisites for the application. This process of initialising your project and toolkit is often called bootstrapping. This can be done easily by using certain commands within your system as follows:
Node.js and Node Package Manager (npm)
Your first prerequisite is to install Node.js and npm on your system, which will allow you to manage dependencies and run JavaScript applications. Alternatively, you can also use yarn instead of npm if you feel like it, as it will serve the same purpose.
Knowledge of JavaScript and React
Redux Toolkit is an extension of Redux, which itself exists as an aid to React.js to simplify the application development process. React.js isn’t a standalone language but rather a framework that helps JavaScript adapt to certain tasks. As a result, using Redux and its Toolkit requires having a foundational knowledge of JavaScript. This familiarity with base technology is especially helpful if you plan to integrate the Redux Toolkit with a React application.
Installing Redux Toolkit
Using the toolkit in redux requires you to add it as a dependency in your project with a command. You can do so by running the following command in the terminal of your system:
npm install @reduxjs/toolkit |
Once you run this command on npm, it will fetch and install the Redux Toolkit package from the official npm registry. Completing the installation process will allow you to use all the powerful features for state management in your application.
Creating a Redux store
A store in Redux functions as a centralised sub, which allows you to store your application’s state. This store contains all the data of the application as well as its UI; hence, it is also referred to as the single source of truth in an application.
The Redux Toolkit simplifies the store creation process, allowing you to focus on managing your application's data flow. To make use of this store, you need to set this up within your application as follows:
import { createStore } from 'redux'; import rootReducer from './reducers'; const store = createStore(rootReducer); |
How to use Redux Toolkit in your project?
Once you understand the prerequisite for using the Redux Toolkit, you can implement it within your project. You can use the toolkit in a real-life project by following the steps:
Creating your first slice
A slice in redux essentially refers to a portion of the store within Redux, which comes with a reducer function and actions. These actions and reducers allow you to modify a specific part of your application’s state and organise your code more efficiently. Slice can simplify your codebase by removing the need to handle actions and reducers separately.
In the code presented below, you can create a file named “todoSlice.js” in your file directory, which will define the slice. You can create your slice by defining the reducer function and actions in your code as follows:
// todoSlice.js import { createSlice } from '@reduxjs/toolkit'; const todoSlice = createSlice({ name: 'todos', initialState: [], reducers: { addTodo: (state, action) => { state.push({ id: Date.now(), text: action.payload, completed: false, }); }, removeTodo: (state, action) => { return state.filter((todo) => todo.id !== action.payload); }, }, }); export const { addTodo, removeTodo } = todoSlice.actions; export default todoSlice.reducer; |
Setting up Redux store
You can set up a Redux Store in a file named “store.js” to proceed with further steps. You can achieve this by using the following code:
// store.js import { configureStore } from '@reduxjs/toolkit'; import todoReducer from './todoSlice'; const store = configureStore({ reducer: { todos: todoReducer, }, }); export default store; |
Integrating Redux in react
Redux allows you to integrate it into your application by using a React component file. In this instance, the React component file is “App.js”, which will allow you to wrap your application with a provider component. This makes it necessary to import the important components for the process in the file directory:
// App.js import React from 'react'; import { Provider } from 'react-redux'; import store from './store'; import './App.css'; function App() { return ( {/* Your application components */} ); } export default App; |
Using Redux actions in components
Redux uses Actions to carry information, which allows you to understand what the necessary changes you need to make in the application’s state. In the instance of a counter slice, you will need to define action creators which encapsulate the logic for incrementing or decrementing counters. These action creators are responsible for triggering changes in the counter-state.
Redux Toolkit makes it easier to manage state modifications and actions for different parts of your application. This process enhances the clarity and maintainability of code while making the process of handling state changes much more efficient. You can also use Redux actions in your components to add and remove to-do items by using the code below:
// AddTodo.js import React, { useState } from 'react'; import { useDispatch } from 'react-redux'; import { addTodo } from './todoSlice'; function AddTodo() { const [text, setText] = useState(''); const dispatch = useDispatch(); const handleAddTodo = () => { if (text.trim() !== '') { dispatch(addTodo(text)); setText(''); } }; return ( type="text" value={text} onChange={(e) => setText(e.target.value)} />
); } export default AddTodo; |
Displaying and removing to-do items
Displaying or removing from a do list of items within applications refers to a list of tasks which need to be performed within a software. This process requires an option to delete tasks or mark them as complete within the system. You can achieve this in your application by using the following code:
// TodoList.js import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { removeTodo } from './todoSlice'; function TodoList() { const todos = useSelector((state) => state.todos); const dispatch = useDispatch(); const handleRemoveTodo = (id) => { dispatch(removeTodo(id)); }; return ( {todos.map((todo) => ))}
); } export default TodoList; |
Finalise your application
Now, you can integrate these components into your application as needed. Don't forget to import and render them within your App.js file or other relevant components.
Interested in becoming a Web Developer? Try our Web Development Training today!
Redux Toolkit and the Ducks Pattern
The Ducks Pattern, also known as the "Redux Ducks" pattern, is a proposal for organising and structuring Redux-related code in a more modular and self-contained manner. It aims to simplify the development and maintenance of Redux applications by co-locating all the related Redux logic for a particular feature or slice of the application state into a single module or file. Here is an example code of the Ducks pattern in Redux:
// todos.js (Ducks module for managing to-do items) // Action Types const ADD_TODO = 'todos/ADD_TODO'; const REMOVE_TODO = 'todos/REMOVE_TODO'; const TOGGLE_TODO = 'todos/TOGGLE_TODO'; // Initial State const initialState = { todos: [], }; // Reducer export default function todosReducer(state = initialState, action) { switch (action.type) { case ADD_TODO: return { ...state, todos: [ ...state.todos, { id: Date.now(), // Generate a unique ID (for simplicity) text: action.payload, completed: false, }, ], }; case REMOVE_TODO: return { ...state, todos: state.todos.filter((todo) => todo.id !== action.payload), }; case TOGGLE_TODO: return { ...state, todos: state.todos.map((todo) => todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo ), }; default: return state; } } // Action Creators export function addTodo(text) { return { type: ADD_TODO, payload: text }; } export function removeTodo(id) { return { type: REMOVE_TODO, payload: id }; } export function toggleTodo(id) { return { type: TOGGLE_TODO, payload: id }; } |
This Ducks pattern can be used in your module as follows:
// Import the Ducks module import todosReducer, { addTodo, removeTodo, toggleTodo } from './todos'; // Create a Redux store and combine the reducer const rootReducer = combineReducers({ todos: todosReducer, }); const store = createStore(rootReducer); // Dispatch actions using action creators store.dispatch(addTodo('Buy groceries')); store.dispatch(addTodo('Finish Redux tutorial')); store.dispatch(toggleTodo(1)); store.dispatch(removeTodo(2)); // Access the updated state const updatedState = store.getState(); console.log(updatedState.todos); // Updated to-do list state |
This pattern was introduced as an alternative to the traditional approach of separating actions, action types, and reducers into separate files. Here are the key principles and characteristics of the Ducks Pattern:
Co-location
In the Ducks Pattern, all Redux-related code for a specific feature or slice of state resides within a single module or file. This means that actions, action types, and reducers for a particular feature are grouped together, providing a clear and self-contained structure.
Action types
Action creators are functions responsible for generating actions in Redux. In the Ducks Pattern, these action creators are defined within the same module as the rest of the Redux logic. Action creators can directly reference the action types and payload data within the same module, resulting in more concise and maintainable code.
Action creators
Action creators are functions responsible for generating actions in Redux. In the Ducks Pattern, these action creators are defined within the same module as the rest of the Redux logic. Action creators can directly reference the action types and payload data within the same module, resulting in more concise and maintainable code.
Reducers
The module contains the reducer function, which specifies how the state should change in response to dispatched actions. The reducer function takes the current state and an action as arguments and returns the new state based on the action type.
Exporting
To make the Redux logic accessible to other parts of the application, the Ducks module exports the reducer and any action creators that need to be used elsewhere. Exporting the reducer allows it to be combined with other reducers when configuring the Redux store.
Separation of concerns
Each feature or slice of state in the application has its own Ducks module. This separation of concerns ensures that different parts of the application do not unintentionally interfere with each other. It also minimises the risk of naming conflicts between action types, as each module operates within its scope.
Learn to create beautiful interfaces with UX / UI Design Jumpstart Course today!
Best practices for structuring your Redux Store
Structuring your Redux store is essential for maintaining a scalable and maintainable codebase. In this section, we'll discuss best practices to help you organise and structure your Redux store effectively. Consistent naming conventions are crucial for writing clean and readable Redux code. Adhering to clear naming conventions ensures that your actions, reducers, and slices are easily understandable by you and your team. Here are some naming guidelines:
Actions
There is a list of best practices you can follow while declaring and using action within your codes. Some of these practices are as follows:
1) Use descriptive action-type names that convey the intent of the action. For example, instead of FETCH, use FETCH_USER_PROFILE.
2) Use uppercase letters and separate words with underscores for action-type constants. For example, FETCH_USER_PROFILE instead of fetchUserProfile.
// Bad Practice: Inconsistent and unclear action type names const FETCH = 'FETCH'; const UPDATE_USER = 'UPDATE_USER'; // Good Practice: Descriptive action type names const FETCH_USER_PROFILE = 'FETCH_USER_PROFILE'; const UPDATE_USER_PROFILE = 'UPDATE_USER_PROFILE'; |
Reducers
Use meaningful reducer function names. A reducer's name should indicate the slice of state it manages. For instance, if your reducer handles user authentication, name it authReducer. Keep reducer functions pure and avoid side effects. They should only depend on their input arguments and return a new state without modifying the existing state.
// Bad Practice: Non-descriptive reducer function name function reducer(state, action) { switch (action.type) { // ... } } // Good Practice: Meaningful and descriptive reducer function name function authReducer(state, action) { switch (action.type) { // ... } } |
Slices (Redux Toolkit)
Name your slices descriptively based on the features they manage. For example, if your slice handles to-do items, name it todosSlice. Use the reducer and actions properties the slice exports to access the reducer function and action creators. This enforces consistency and simplifies integration.
import { createSlice } from '@reduxjs/toolkit'; // Bad Practice: Uninformative slice name const badSlice = createSlice({ name: 'bad', // ... }); // Good Practice: Descriptive slice name const todosSlice = createSlice({ name: 'todos', // ... }); |
Testing
Testing your Redux code is crucial to ensure its reliability and maintainability. Here are some testing techniques and tools to consider when working with Redux:
1) Unit Testing: Write unit tests for individual actions, reducers, and selectors. Use testing libraries like Jest and testing utilities provided by Redux Toolkit.
// Example of unit testing a Redux action using Jest import { fetchUser, FETCH_USER_SUCCESS } from './userActions'; test('fetchUser action dispatches FETCH_USER_SUCCESS', () => { const dispatch = jest.fn(); const getState = () => ({}); const apiService = { getUser: jest.fn(() => Promise.resolve({ id: 1, name: 'John' })) }; return fetchUser()(dispatch, getState, { apiService }).then(() => { expect(dispatch).toHaveBeenCalledWith({ type: FETCH_USER_SUCCESS, payload: { id: 1, name: 'John' }, }); }); }); |
2) Integration Testing: Test the interactions between different parts of your Redux store. Ensure that actions correctly trigger state changes in reducers and selectors.
// Example of integration testing a Redux reducer using Jest import authReducer from './authReducer'; import { login, logout } from './authActions'; test('authReducer handles login and logout actions', () => { let state = authReducer(undefined, {}); expect(state.isAuthenticated).toBe(false); state = authReducer(state, login({ id: 1, username: 'user' })); expect(state.isAuthenticated).toBe(true); state = authReducer(state, logout()); expect(state.isAuthenticated).toBe(false); }); |
3) End-to-End Testing: Consider incorporating end-to-end tests that cover Redux interactions as part of your application's testing suite. Tools like Cypress can be valuable for this purpose.
// Example of end-to-end testing Redux interactions using Cypress it('Logs in and logs out', () => { cy.visit('/login'); // Interact with UI elements to perform login cy.get('#username').type('testuser'); cy.get('#password').type('password'); cy.get('button[type="submit"]').click(); // Verify the user is logged in cy.get('#user-profile').should('contain', 'Welcome, testuser'); // Perform a logout action cy.get('#logout-button').click(); // Verify the user is logged out cy.get('#user-profile').should('not.exist'); }); |
4) Mocking: Use mocking libraries or manual mocks to simulate API calls or other external dependencies when testing actions or thunks. When testing actions or thunks that make API calls, you can use mocking libraries like jest.mock to simulate those API calls. Here's an example using Jest:
// Action to fetch user data from an API import axios from 'axios'; export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS'; export const fetchUserSuccess = (user) => ({ type: FETCH_USER_SUCCESS, payload: user, }); export const fetchUser = () => async (dispatch) => { try { const response = await axios.get('/api/user'); dispatch(fetchUserSuccess(response.data)); } catch (error) { // Handle error } }; |
import axios from 'axios'; import { fetchUser } from './userActions'; jest.mock('axios'); test('fetchUser action dispatches FETCH_USER_SUCCESS', async () => { const dispatch = jest.fn(); const mockUser = { id: 1, name: 'John' }; axios.get.mockResolvedValue({ data: mockUser }); await fetchUser()(dispatch); expect(dispatch).toHaveBeenCalledWith({ type: FETCH_USER_SUCCESS, payload: mockUser, }); }); |
5) Redux DevTools: Leverage the Redux DevTools extension for browser-based debugging during development. It allows you to inspect state changes, actions, and dispatch history. Using the Redux DevTools extension in your application allows you to inspect state changes, actions, and dispatch history during development. Integration typically involves adding a middleware to your Redux store. Below is an example using the Redux Toolkit:
import { configureStore } from '@reduxjs/toolkit'; import rootReducer from './reducers'; // Add the Redux DevTools extension middleware const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()), }); |
6) Snapshot Testing: For complex components connected to Redux, consider using snapshot testing libraries like react-test-renderer or Enzyme to capture component snapshots and identify unintended changes. Snapshot testing is often used to capture the visual output of components, including those connected to Redux. Here's a basic example using Jest and react-test-renderer:
import React from 'react'; import renderer from 'react-test-renderer'; import ConnectedComponent from './ConnectedComponent'; test('ConnectedComponent renders correctly', () => { const tree = renderer.create( expect(tree).toMatchSnapshot(); }); |
7) Redux Toolkit Testing Utilities: If you're using Redux Toolkit, take advantage of the testing utilities it provides, such as createSlice's reducers property for testing reducers.
import { increment, decrement, counterReducer } from './counterSlice'; test('counterReducer increments correctly', () => { const initialState = 0; const newState = counterReducer(initialState, increment()); expect(newState).toBe(1); }); |
8) Continuous Integration (CI): Integrate testing into your CI/CD pipeline to ensure your Redux code is tested automatically with each code change. If you're using Redux Toolkit, it provides testing utilities to simplify the testing of reducers and actions created using createSlice. Here's an example of testing a reducer using Redux Toolkit's createSlice:
stages: - test - build test: stage: test script: - npm install - npm test build: stage: build script: - npm build |
Interested in being a developer? Try our App & Web Development Training today!
Conclusion
Redux Toolkit simplifies and streamlines the process of using Redux in your React applications. It reduces boilerplate, improves the developer experience, and encourages structured code. Following the steps outlined above, you can easily integrate the Redux Toolkit into your project and take advantage of its benefits.
Frequently Asked Questions
Upcoming Programming & DevOps Resources Batches & Dates
Date
Fri 6th Dec 2024
Fri 21st Feb 2025
Fri 25th Apr 2025
Fri 20th Jun 2025
Fri 22nd Aug 2025
Fri 17th Oct 2025
Fri 19th Dec 2025