As React developers, we know that state management is a critical aspect of building complex applications. Redux has been a popular choice for managing state in large-scale React projects. However, setting up and maintaining a Redux store can become cumbersome, leading to boilerplate code and reduced developer productivity.
Redux Toolkit comes to the rescue, providing a streamlined and opinionated way to use Redux efficiently. It simplifies the setup process, reduces boilerplate, and offers advanced features for handling asynchronous operations, optimized performance, and more.
Here, I will give an overview of the Redux Toolkit. Redux toolkit, previously known as Redux Starter kit, is the official, opinionated, batteries-included toolset for efficient Redux development. This excellent tool is part of the core Redux toolkit package, which is designed to simplify common Redux use cases. The motto of Redux toolkit is to provide good defaults to reduce the boilerplate code and complexity in managing the global state of your React app.
Stop for a moment and think of the toolkit as a Redux store, carrying all convenient packaging for your Redux development. The main redux toolkit packages are @reduxjs/toolkit and react-redux. Importantly, Redux toolkit automatically generates actions and reducers, reducing the need to write boilerplate code. And let me tell you something else, Redux Toolkit includes Redux Thunk for async logic. Isn't it exciting?
Enough of the talk, let's see how you can use the redux toolkit in your react app:
1 import { configureStore } from '@reduxjs/toolkit'; 2 import rootReducer from './reducer'; 3 4 const store = configureStore({ 5 reducer: rootReducer 6 }); 7 8 export default store; 9
In the code snippet above, I have imported the configureStore method from the core Redux toolkit package. This function sets up the redux store with the root reducer and some default middleware from the Redux toolkit.
Don't worry, now, we're getting into the real Redux logic. To write Redux logic more effectively, Redux Toolkit offers some powerful abstractions such as createAsyncThunk, createSlice, and createEntityAdapter.
createAsyncThunk is part of the Redux Toolkit package, and let me tell you, it's a game-changer! It simplifies managing async operations where you need to dispatch an action before data fetching, upon successful data retrieval, or when encountering errors.
Here's a quick look at how we can use createAsyncThunk in our Redux Toolkit application:
1 import { createAsyncThunk } from '@reduxjs/toolkit'; 2 import api from 'path/to/api'; 3 4 export const fetchData = createAsyncThunk( 5 'points/fetch', 6 async () => { 7 const response = await api.get('/points'); 8 return response.data; 9 } 10 ); 11
In this code snippet, fetchData is the action creator function that uses the createAsyncThunk method. It automatically generates three action types (request, success, failure) saving us from writing extra boilerplate code.
Redux Toolkit's createSlice automatically generates reducer functions and corresponding action creators based on the reducers and name options provided.
If we integrate createSlice and createAsyncThunk in the same reducer function, integration looks something like this:
1 import { createSlice } from '@reduxjs/toolkit'; 2 import { fetchData } from './actions'; 3 4 const dataSlice = createSlice({ 5 name: 'data', 6 initialState: { entities: [], loading: 'idle' }, 7 reducers: {}, 8 extraReducers: (builder) => { 9 builder 10 .addCase(fetchData.pending, (state) => { 11 state.loading = 'loading'; 12 }) 13 .addCase(fetchData.fulfilled, (state, action) => { 14 state.loading = 'idle'; 15 state.entities = action.payload; 16 }); 17 }, 18 }); 19 20 export default dataSlice.reducer; 21
Here, we've defined a slice of Redux store function for data fetching. With the actions generated by createAsyncThunk, we've defined different states based on action outcomes.
1 const adapterTemplate = createEntityAdapter({ 2 selectId: template => template.id, 3 sortComparer: (a, b) => a.title.localeCompare(b.title) 4 }) 5
This code block shows how the createEntityAdapter comes in handy to manage normalized data in the Redux store.
Immer is a JavaScript package that simplifies the process of writing immutable update logic in your Redux reducers. The beauty of Immer is that it enables you to write code that looks like mutable logic, while actually applying immutable updates under the hood.
Integrating Immer allows us to focus on writing simpler and more readable reducer functions without worrying about accidentally muting the state. Redux Toolkit leverages the advantages of Immer as it's used under the hood in createSlice and createReducer.
Here's a glance at a reducer function in a React redux toolkit with Immer:
1 const todosSlice = createSlice({ 2 name: 'todos', 3 initialState: [], 4 reducers: { 5 addTodo: (state, action) => { 6 state.push({text: action.payload, completed: false}) 7 }, 8 toggleTodo: (state, action) => { 9 const todo = state[action.payload] 10 todo.completed = !todo.completed 11 } 12 } 13 }) 14
Notice the nice and clean reducer function. Within addTodo, the push operation is made directly on the state without returning a new state array, thanks to Immer. Similarly, toggleTodo is directly updating the needed todo object's completed property without any mutations.
When we use the redux toolkit core methods such as createSlice, behind the scenes, it uses the infrastructure provided by Immer for the Redux store and state updates.
React Redux toolkit was designed with the larger goal of simplifying the redux logic, data fetching and eliminating large quantities of boilerplate code from a developer's life.
One of the powerful features provided by the Redux toolkit package is support for middleware integration which is utilized by Redux applications extensively. Middleware lets you add extra control or processing to your Redux flow. The Redux toolkit core package leverages redux-thunk for async middleware support, but we can certainly add support for other middleware such as redux-saga.
That isn't all, though. It also provides configurable middleware for additional logic out-of-box. To expand on this, here's how you could customize your middleware:
1 import { configureStore } from '@reduxjs/toolkit'; 2 import logger from 'redux-logger'; 3 import rootReducer from './rootReducer'; 4 5 const store = configureStore({ 6 reducer: rootReducer, 7 middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger) 8 }); 9 10 export default store; 11
In this example, the redux-logger middleware is concatenated to the list of default middleware provided by @reduxjs/toolkit.
Redux toolkit does an excellent job assisting us with its capabilities in creating custom Redux middleware as well.
Here's an example of how Redux Toolkit simplifies the process:
1 export const actionLogger = storeAPI => next => action => { 2 console.log('dispatching', action.type) 3 let result = next(action) 4 console.log('state after dispatch', storeAPI.getState()) 5 return result 6 } 7
You can apply this custom middleware by following the getDefaultMiddleware() concatenation pattern I showed you earlier.
Let's now integrate our Redux Toolkit with React in our feature-packed journey. Using Redux with react-redux's integration is a norm with modern apps that manages a vast and intricate global store.
You might already know about react-redux Provider and connect. Before Redux Toolkit, we used to integrate our Redux store function with the provider component by wrapping the React app in the Provider component. We are using here @reduxjs/toolkit in place of classic redux and react-redux packages.
1 import { Provider } from 'react-redux'; 2 import store from './app/store'; 3 import App from './App'; 4 5 const RootApp = () => ( 6 <Provider store={store}> 7 <App /> 8 </Provider> 9 ); 10 11 export default RootApp; 12 13
Now every react-redux component underneath Provider in the component tree can access the Redux store.
Selectors are functions that know how to extract specific pieces of information from your Redux store. They are a vital aspect of state management within React-Redux applications, helping to keep your state structure as minimal as possible.
In the Redux Toolkit, when we define a createSlice, it automatically generates selectors. You can see that in action here:
1 import { useSelector } from 'react-redux'; 2 3 4 function CurrentUser() { 5 const user = useSelector(state => state.user.value); 6 7 return ( 8 <div> 9 Current User: {user} 10 </div> 11 ) 12 } 13
The useSelector hook in the react-redux library is used to extract data from the Redux store state by calling our state.user.value slice. We can avoid the bother to write switch statements, and this is where Redux Toolkit is a favorite!
Testing is love for we developers! Especially when it comes to the React and Redux toolkit. Redux toolkit makes testing your components, actions, and reducers a lot easier with the tools provided in its arsenal.
Most of the time, you would want to write unit tests for the most meaningful parts of your app. For Redux Toolkit applications, as createSlice() generates both reducers and actions, it's advisable to write tests that cover these two aspects. Check out how you can do that below:
1 import todos, { addTodo, toggleTodo } from './todosSlice' 2 3 describe('todos reducer', () => { 4 const initialState = [] 5 it('should handle initial state', () => { 6 expect(todos(undefined, { type: 'unknown' })).toEqual([]) 7 }) 8 9 it('should handle addTodo', () => { 10 const actual = todos(initialState, addTodo('Run the tests')) 11 expect(actual.length).toBe(1) 12 }) 13 14 it('should handle toggleTodo', () => { 15 const actual = todos([ {text: 'Run the tests', completed: false} ], toggleTodo(0)) 16 expect(actual[0].completed).toBe(true) 17 }) 18 }) 19
In the above unit test, I am testing the todos slice functions that were automatically created using createSlice().
So, you've been working with React and Redux Toolkit and thought about how to persist your Redux store? Like you, many developers want their applications to maintain their state even after the page is refreshed. Thanks to Redux Toolkit, persisting state to local storage has never been easier!
The simplest way to persist the state is to use the local storage API of the browser where the redux state is stringified to be stored and parsed when retrieved.
Here is how you tackle it with Redux Toolkit:
1 import { configureStore } from '@reduxjs/toolkit'; 2 import rootReducer from './rootReducer'; 3 4 const persistedState = localStorage.getItem('reduxState') 5 ? JSON.parse(localStorage.getItem('reduxState')) 6 : {} 7 8 const store = configureStore({ 9 reducer: rootReducer, 10 preloadedState: persistedState 11 }); 12 13 store.subscribe(()=>{ 14 localStorage.setItem('reduxState', JSON.stringify(store.getState())) 15 }) 16 17 export default store; 18
The subscribe function is used to listen for state changes, writing the state to the localStorage API whenever a state change occurs.
And voila - our Redux Toolkit state is now persisting, even on refreshing pages. Though, we should know that this approach can be further optimized as manual synchronization can be problematic in advanced use cases.
As a developer, you're already aware of how invaluable DevTools can be for debugging our applications. The same stands true for Redux development. The Redux DevTools extension provides us with some powerful features like action history, state diff, and even hot reloading.
Your journey towards efficient Redux development wouldn't be complete without the Redux DevTools. Redux Toolkit provides out-of-the-box support for the Redux DevTools browser extension.
A simple configuration can save us from writing tedious lines of code to implement redux-devtools:
1 import { configureStore } from '@reduxjs/toolkit'; 2 import rootReducer from './rootReducer'; 3 4 const store = configureStore({ 5 reducer: rootReducer, 6 devTools: process.env.NODE_ENV !== 'production', 7 }); 8 9 export default store; 10
In our Redux store configuration, the devTools option is set to true automatically if NODE_ENV is not 'production'.
The time travel feature of the Redux DevTools extension is incredibly powerful - it lets us step forwards and backwards through the history of state changes, and even lets us modify it! If the current state of your app isn't optimal, the official opinionated batteries included toolset for efficient redux development provides you with the tools to easily review and modify previous states.
Let's seal the deal with some best practices! Employ these techniques in your react and redux toolkit adventures and you'll find your redux development experience streamlined.
One common challenge developers face is managing a scalable and robust folder structure for large applications. Your Redux Toolkit should include a scalable folder structure that separates concerns into different folders:
In Redux, actions, reducers, and selectors have unique roles. Keep them focused on their respective roles:
Try to make a habit of using Redux Toolkit's createSlice and createAsyncThunk. This will not only generate action creators and reducers automatically but will keep them close to each other. As your app grows, so do the convenience and effectiveness of these practices.
We have navigated through the vast sea of React Redux Toolkit in this blog post, starting from understanding the basic concepts to more advanced features like asynchronous operations, middleware, and custom hooks. The Redux Toolkit targets to make Redux fun again by reducing the boilerplate code and bringing more simplicity to implement complex state management problems.
The concept we discussed throughout the course can be a game-changer in how developers deal with Redux in their applications. Just like the Redux Toolkit, there are unique tools developed to simplify our development process, one such tool being WiseGPT by DhiWise.
Just as Redux Toolkit has contributed to making state management more efficient, WiseGPT makes it easier for developers to integrate APIs into their React projects. It's an AI-powered plugin that learns and mirrors your coding style for auto-creating models and functions.
We've sailed across many Redux Toolkit concepts, realized its potential in efficient Redux development, and explored how it significantly reduces the need for boilerplate code. Redux Toolkit, as an official opinionated batteries-included toolset for Redux, has truly made Redux accessible, fun, and enjoyable.
So next time you hear anyone boasting too much boilerplate code in Redux, give them a friendly nod and let them in on the fascinating world of Redux Toolkit.
Looking forward to our next journey together - until then, stay curious, stay coding!
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.