Table of contents
- Introducing Redux Toolkit โจ
- Step 1: Setting Up a Vite Project and Installing Redux Toolkit ๐
- Step 2: Creating an Empty Store ๐
- Step 3: Defining Initial State, Reducers, and Actions ๐งฉ
- Current State of counterSlice.js ๐ฆ
- Step 4: Connecting the Counter Slice to the Redux Store
- Part 5: Usage in React Component ๐ฌ
- Part 6: Integration with Existing React Application
- Source Code ๐
Redux, an open-source JavaScript library, plays a pivotal role in managing the state of an application. While React provides a simple way to manage state within components, Redux steps in when the need for centralized state management arises. However, one common criticism has been the amount of boilerplate code required.
Introducing Redux Toolkit โจ
To address the redundancy of traditional Redux setups, the Redux team introduced the Redux Toolkit. It is a set of tools and conventions designed to simplify working with Redux, making it more approachable and developer-friendly. Redux Toolkit dismiss much of the boilerplate code, allowing developers to focus more on building features rather than configuring state management.
Step 1: Setting Up a Vite Project and Installing Redux Toolkit ๐
Let's start by creating a new Vite project and installing the necessary dependencies for Redux Toolkit. Open your terminal and execute the following commands:
npx create-vite@latest my-redux-app --template react
cd my-redux-app
npm install @reduxjs/toolkit react-redux
This will set up a new Vite project with React and install the required packages for Redux Toolkit.
Step 2: Creating an Empty Store ๐
Now that our project is set up with Redux Toolkit, the next step is to create an empty Redux store. Open your project in a code editor and locate the entry file (usually src/main.js
or src/index.js
). Add the following code:
// src/main.js
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer: {},
});
In this step, we've imported configureStore
from Redux Toolkit and used it to create a basic Redux store. The reducer
field is currently empty, and as your application grows, you'll add reducers to manage different parts of the state.
Step 3: Defining Initial State, Reducers, and Actions ๐งฉ
In Redux Toolkit, understanding the initial state, reducers, and actions is crucial for effective state management. Let's delve into each of these concepts and how they play a role in shaping the behavior of your Redux store.
Initial State in Redux Toolkit ๐
In Redux, the initial state represents the starting point of your application's state tree. It defines the structure and default values of your state before any actions are dispatched. In our example, we'll create a simple counter slice with an initial state of 0.
import { createSlice } from '@reduxjs/toolkit';
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {},
});
// This is not allowed in React directly, but it works in Redux due to the immer library.
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
Here, the initialState
is set to an object with a property value
initialized to 0, reflecting the starting point of our counter. Additionally, it includes a comment about directly modifying the state, which is facilitated by the immer library behind the scenes in Redux Toolkit.
Reducers and Actions ๐ง
Reducers are functions that specify how the state changes in response to dispatched actions. Actions, on the other hand, are payloads of information that send data from your application to the Redux store. In our example, we'll define increment
and decrement
actions along with their corresponding reducers.
import { createSlice } from '@reduxjs/toolkit';
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1;
// This is not allowed in React directly, but it works in Redux due to the Immer library.
},
decrement: (state) => {
state.value -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
Here, we've incorporated the provided code defining the increment
and decrement
actions along with their reducers. The comments within the code highlight the specific behavior of updating the state directly within reducers, a capability enabled by the immer library.
Exporting Reducers ๐
To make these reducers accessible to our Redux store, we need to export them. This allows us to combine them in the main store configuration.
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
const store = configureStore({
reducer: {
counter: counterReducer,
},
});
export default store;
In this step, we've incorporated our counterSlice
reducer into the Redux store. As your application grows, you can create additional slices for different features, keeping your state management organized and modular.
Current State of counterSlice.js ๐ฆ
Let's take a closer look at the current implementation of counterSlice.js
in our Redux setup. This file encapsulates the logic for managing the state related to our counter feature.
import { createSlice } from '@reduxjs/toolkit';
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1;
// Directly modifying the state is not allowed in react but it is allowed in redux due to the Immer library which works behind the scene
},
decrement: (state) => {
state.value -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
In this snippet, we've defined the counterSlice
using the createSlice
function from Redux Toolkit. The initialState
is set to an object with a property value
initialized to 0, representing the starting point of our counter.
Reducers and Actions
The reducers
section contains two key functions: increment
and decrement
. These functions define how the state should change in response to corresponding actions. The increment
reducer increases the value
property by 1, while the decrement
reducer decreases it by 1.
Immer Library in Action ๐ญ
A notable point is the comment regarding the direct modification of the state within the reducers. While directly updating the state is not allowed in React, it works seamlessly in Redux thanks to the immer library behind the scenes. Immer enables a more straightforward way of updating state by allowing mutable updates in a seemingly immutable environment.
Exporting Reducers and Actions ๐
Finally, we export the increment
and decrement
actions, as well as the entire reducer. These exports will be utilized in the main store configuration to enable the integration of this counter feature into our Redux store.
This completes the current state of counterSlice.js
. As we proceed, we'll explore how to connect these actions with React components and dynamically manage the state of our application.
Step 4: Connecting the Counter Slice to the Redux Store
Now that we've defined our
counterSlice
with initial state, reducers, and actions, let's connect it to the Redux store. This step ensures that our application can leverage the centralized state management offered by Redux.
In the main store configuration file, often found at src/app/store.js
, import the counterSlice
and configure the store:
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
const store = configureStore({
reducer: {
counter: counterReducer,
},
});
export default store;
Here, we import the counterSlice
and include its reducer, named counterReducer
, in the Redux store configuration under the counter
key. This establishes the connection between the counterSlice
and the Redux store.
As your application grows, you can follow a similar pattern to connect additional slices, maintaining an organized and scalable state management structure.
Part 5: Usage in React Component ๐ฌ
Overview
In a React application that incorporates Redux, the useSelector
and useDispatch
hooks from the react-redux
library are crucial for interacting with the Redux store. Let's delve into how each of these hooks works.
useSelector
- Reading Data from the Redux Store
The useSelector
hook allows components to access and read data from the Redux store. It takes a selector function as an argument, specifying which part of the state to extract.
useDispatch
- Accessing the dispatch
Function
The useDispatch
hook provides a reference to the dispatch
function from the Redux store. This function is crucial for dispatching actions, initiating state updates.
Part 5: Usage in a React Component ๐ฌ
Now, let's illustrate the usage of useSelector
and useDispatch
in a React component that interacts with a Redux store.
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from '../features/counter/counterSlice';
const Counter = () => {
// Reading data from the Redux store
const counter = useSelector((state) => state.counter.value);
// Accessing the dispatch function
const dispatch = useDispatch();
return (
<div>
<h1>Counter: {counter}</h1>
{/* Dispatching the increment action */}
<button onClick={() => dispatch(increment())}>Increment</button>
<button onClick={() => dispatch(decrement())}>Decrement</button>
</div>
);
};
export default Counter;
Explanation ๐
useSelector
: In this example,useSelector
reads thecounter
value from the Redux store. The selector function extracts thevalue
property from thecounter
slice.useDispatch
: TheuseDispatch
hook provides access to thedispatch
function, which is then used to dispatch theincrement
anddecrement
actions when the corresponding buttons are clicked.Component Rendering: The component renders the current counter value and two buttons. Clicking these buttons triggers the dispatching of the associated actions, leading to state updates in the Redux store.
Part 6: Integration with Existing React Application
Utilizing the Counter Component
Now that we have defined the Counter
component using useSelector
and useDispatch
, let's integrate it into our React application.
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './app/store';
import Counter from './app/components/Counter';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
// <StrictMode>
<Provider store={store}>
<Counter />
</Provider>
// </StrictMode>
);
Explanation ๐
Import Libraries and Components: We import the necessary libraries, including
createRoot
from "react-dom/client,"Provider
from "react-redux," and our Redux store (store
). Additionally, we import theCounter
component that we defined earlier. Ensure you adjust the path to theCounter
component based on your project structure.Create a Root Container: Using
createRoot
and obtaining the root container, we set up the environment for rendering our React application.Render the Counter Component: We render the
Counter
component within the Redux storeProvider
. This ensures that theCounter
component can access and interact with the state managed by the Redux store.
Conclusion ๐
By integrating the Counter
component into our React application and providing it with the Redux store through the Provider
, we establish a connection between the component and the centralized state management system. Now, the Counter
component can dynamically read data from the Redux store using useSelector
and dispatch actions using useDispatch
.
Source Code ๐
Explore the entire source code for the steps discussed in this tutorial below on Replit. If you encounter any issues or have questions, feel free to reach out to me directly. I'm here to help! Connect with me on: