Simplifying State Management in React with Redux Toolkit

Rishabh
JavaScript Journal: Unlocking Project Potential
7 min readApr 29, 2024

Redux Toolkit, the official, opinionated, batteries-included toolset for efficient Redux development. It’s Redux, but it's more straightforward, powerful, and has less code. Let’s demystify Redux and discover how Redux Toolkit streamlines your React projects.

Understanding Redux in React

At its heart, Redux is a state management library that helps you manage the “global” state of your React application—the kind of state that needs to be shared across many parts of your application. Redux centralises this state in a single store, making it accessible to any component that needs it without prop drilling or context providers.

The Case for Redux Toolkit

While Redux provides predictability and makes state changes traceable, it has often been criticised for the amount of boilerplate code it requires. Redux Toolkit answers this by abstracting the tedious parts, reducing boilerplate, and preventing typical mistakes.

Core Concepts of Redux Toolkit

Redux Toolkit packages Redux's goodness along with some helpful utilities. Here’s what you get out of the box:

  • configureStore: Simplifies store setup with reasonable defaults.
  • createReducer and createSlice: Reduces the boilerplate in writing reducers and auto-generates action creators and action types.
  • createAsyncThunk: Handles async logic without writing a reducer.
  • createSelector: Generates memoised selectors to optimise state selection.

Real-Life Scenarios with Redux Toolkit

Let’s walk through some typical scenarios in a React application that manages a list of users and how Redux Toolkit makes life easier.

  1. Grab List of Users: Fetching data is a common task. In a traditional Redux setup, you’d need action types, action creators, and reducers. With Redux Toolkit’s createAsyncThunk, you define an async function and get all the related actions for free. It's like going from manual transmission to automatic; you focus on the driving, not the gear shifting.
  2. Delete a User or the Whole List of Users: Deletion is another common operation. Previously, you might write separate action types, creators, and reducer cases for deleting one user versus all. Redux Toolkit’s createSlice comes to the rescue, bundling all related logic in one place. You define what happens on delete; Redux Toolkit handles the rest.
  3. Use createAsyncThunk: Handling asynchronous actions like API calls in Redux usually involves middleware like redux-thunk or redux-saga. createAsyncThunk simplifies this by providing a pattern for defining async functions that automatically dispatch lifecycle actions. It's like having a seasoned butler who knows precisely when to serve your guests so you can enjoy the party.

What is createAsyncThunk?

createAsyncThunk is a function provided by Redux Toolkit that simplifies handling asynchronous operations within a Redux application. It abstracts the boilerplate logic in dispatching actions related to an asynchronous process, such as an API call. Let's dive into its usage with a real-life example.

Understanding createAsyncThunk:

Signature: createAsyncThunk(actionType, payloadCreator)

  1. actionType: A string that defines the prefix for the generated action types.
  2. payloadCreator: A callback function that performs the async operation and returns a promise.

How createAsyncThunk Works:

createAsyncThunk generates an action creator that handles the promise lifecycle automatically by dispatching actions based on the promise resolution:

  • Dispatches an action with type actionType/pending when the promise is initiated.
  • Dispatches an action with type actionType/fulfilled when the promise is successfully resolved.
  • Dispatches an action with type actionType/rejected when the promise is rejected.

Let’s build a react project to see these concepts live

Step 1: Create a react project using CRA (create react application)

npx create-react-app redux-thunk-ts --template typescript

then run the project to see if everything is fine

cd redux-thunk-ts && npm start

You should see the screen below.

Step 2: Remove extra files

When you create a react project using CRA, the folder structure will look like the below:

Folder structure of a react project — 1

But for now, we will remove some extra files, keep them clean, and focus on learning redux.

Folder structure of a react project — 2

Copy and paste the code below into src/index.tsx file.

// file: src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);

root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

Copy and paste the code below into src/App.tsx file.

// file: src/App.tsx

function App() {
return (
<div className='App'>
<h1>React Redux App</h1>
</div>
);
}

export default App;

Then restart the React app, and you will see the app below.

React app in the browser.

Step 3: Setup redux in our projects

Let’s add the required dependencies. Run the below command to add @reduxjs/toolkit and react-redux. We don’t have to install ‘redux’ separately as it is already bundled with @reduxjs/toolkit.

npm i @reduxjs/toolkit react-redux

Step 4: Create a redux slice.

Create a folder inside src, name it a store, and two files, user-slice.ts and store.ts. Your folder structure should look like this:

Step 5: Copy and paste below code to src/store/user-slice.ts

// file: src/store/user-slice.ts

import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit';

// Define a type for the user object
interface User {
id: number;
name: string;
}

// Define a type for the slice state
interface UserState {
users: User[];
isLoading: boolean;
error?: string;
}

// Define the initial state using the `FetchState` type
const initialState: UserState = {
users: [],
isLoading: false,
};

// Create the async thunk for fetching users
export const fetchUsers = createAsyncThunk<User[]>(
'userSlice/fetchUsers',
async () => {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users'
);
const users: User[] = await response.json();
return users;
}
);

export const userSlice = createSlice({
name: 'userSlice',
initialState,
reducers: {
// Use the `PayloadAction` type to define the payload shape
onDelete(state) {
state.users = [];
},
onDeleteParticular(state, action: PayloadAction<number>) {
const index = action.payload;
const modifiedUsers = [...state.users];
modifiedUsers.splice(index, 1);
state.users = modifiedUsers;
},
},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.isLoading = true;
})
.addCase(
fetchUsers.fulfilled,
(state, action: PayloadAction<User[]>) => {
state.isLoading = false;
state.users = action.payload;
}
)
.addCase(fetchUsers.rejected, (state, action) => {
state.isLoading = false;
state.error = action.error.message;
});
},
});

export const { onDelete, onDeleteParticular } = userSlice.actions;

// The reducer should be exported as default
export default userSlice.reducer;

Step 6: Copy and paste the code below in the src/store/store.ts file.

import { configureStore } from '@reduxjs/toolkit';
import { userSlice } from './user-slice';

const reducer = {
fetch: userSlice.reducer,
};

export const store = configureStore({ reducer });

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

Step 7: Copy and paste the code below in the src/App.ts file.

import { useDispatch, useSelector } from 'react-redux';
import { fetchUsers, onDelete, onDeleteParticular } from './store/user-slice';
import { AppDispatch, RootState } from './store/store';

export default function App() {
const users = useSelector((state: RootState) => state.fetch.users);
const isLoading = useSelector((state: RootState) => state.fetch.isLoading);
const dispatch: AppDispatch = useDispatch();

return (
<div>
<button
onClick={() => {
dispatch(fetchUsers());
}}>
Get Users
</button>
<button
onClick={() => {
dispatch(onDelete());
}}>
Delete Users
</button>
{isLoading}
{users.length > 0
? users.map((user, key) => (
<div key={user.id}>
{user.name} {key + 1}
<button
onClick={() => {
dispatch(onDeleteParticular(key));
}}>
X
</button>
</div>
))
: ''}
</div>
);
}

Step 8: Copy and paste the below code in src/index.tsx file.

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
import { Provider } from 'react-redux';
import { store } from './store/store';

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);

root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);

Step 9: Give a basic style to the app. Copy and paste the below code in the src/index.css file.

/* file: src/index.css */
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: burlywood;
padding: 5rem;
}

button {
padding: 0.5rem 1rem;
font-size: 1rem;
margin: 0.5rem;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

Step 10: Test the code by restarting the React app. Run the below line in the terminal.

npm run start

Your app should be like the below in the browser:

When you click the Get Users button, the UI will look like this

--

--

Rishabh
JavaScript Journal: Unlocking Project Potential

👋 React wizard by day, guitar hero by night. I write code that even my bugs get applause! On a quest to make UI/UX spell "U I'm Excited!" 🎸🤓