Part 2: Moving an existing redux application to the redux toolkit incrementally

In the previous part, we set up a GitHub profile finder application using React, Redux, and TypeScript. Now, it’s time to take things up a notch by migrating it to Redux Toolkit.

Spoiler alert: we’ll be deleting a lot of code!

Don’t believe me? Just look at this committed image!

Deleted files

Why Redux toolkit?

Link for an explanation on official site

Let me put it in my own words. In the previous article, we covered the basics of Redux and its usage. Now, let’s delve into understanding Redux Toolkit, often referred to as RTK.

Imagine you’re coding a complex application, and managing the state feels like solving a giant jigsaw puzzle. Traditional Redux is like trying to put the puzzle together without a picture: setting up actions, reducers, and middleware from scratch. It works, but it’s time-consuming, repetitive, and leaves plenty of room for errors.

Now, Redux Toolkit is like having GitHub Copilot by your side. It automatically suggests the best code snippets, completes functions for you and ensures you’re following best practices without you even asking. It streamlines your coding process, reduces boilerplate, and helps you avoid common mistakes.

Simplified Setup and ConfigurationJust like GitHub Copilot sets up your coding environment, Redux Toolkit provides configureStore which sets up your Redux store with sensible defaults, including useful middleware and DevTools support.

2. Automatic Code Generation

Redux Toolkit’s createSlice function automatically generates action creators and action types, similar to how Copilot generates code snippets. This reduces boilerplate and simplifies your Redux logic.

3. Best Practices by Default

Copilot suggests code following best practices. Similarly, Redux Toolkit encourages best practices by default, ensuring your code is clean, efficient, and maintainable.

4. Built-in Middleware

Like Copilot handling repetitive tasks, Redux Toolkit includes useful middleware like redux-thunk out of the box, so you don’t have to set it up manually.

4. Efficient and Predictable State Management

Redux Toolkit uses Immer for immutable state updates under the hood, making state management more predictable and efficient, much like how Copilot makes code suggestions that follow a logical, predictable pattern.Enough with the drama let’s see some action

Previously on “Migration to Redux Toolkit”

We built a GitHub profile finder with two reducers: users and repos, along with their respective action creators and actions. Now, let’s see how we can move this to Redux Toolkit and make our lives a whole lot easier.

Starter code: https://github.com/suhas86/git-clone-react-redux/tree/feature/old-redux

Migrating the foundation of Redux state — the store

As mentioned in the first point, Redux Toolkit offers a straightforward setup and configuration. It bundles DevTools, Redux Thunk, and Immer right out of the box, making store setup a breeze.

Install redux toolkit npm install @reduxjs/toolkit

Below, you can see how we’ve eliminated the manual setup of rootReducer DevTools and done away with the need to set up redux-thunk middleware.

import { configureStore } from “@reduxjs/toolkit”;
import { reposReducer } from “../reducers/reposReducer”;
import { usersReducer } from “../reducers/usersReducer”;

export const store = configureStore({
reducer: {
users: usersReducer,
repos: reposReducer,
}
})

export type ApplicationState = ReturnType<typeof store.getState>;
export type ApplicationDispatch = typeof store.dispatch;Before and AfterBeforeAfter

🚨Note: You will see a TypeScript error in the store file. The error message suggests that unknown is not a valid type for the Action type from Redux, which expects a string as its type parameter. This string should represent the type of action.

In Redux, every action should have a type property that is a string. This type property is used to identify the action.

You can ignore this as this will be handled once we create a slice. If you’re incrementally moving your application to Redux Toolkit and wish to address this issue beforehand:

Define UnknownAction as an action type with a type property that is a string in your type definitions. Here’s how you can do it:

type UnknownAction = Action<string>;Reference commit: https://github.com/suhas86/git-clone-react-redux/commit/93361311ecb0c825e4dddf610eff13c3f0b53881

🚨 One more error:

In the home page, we will see an error

No overload matches this call. The last overload gave the following error. Argument of type ‘ToggleUserLikeAction’ is not assignable to parameter of type ‘UnknownAction’. Index signature for type ‘string’ is missing in type ‘ToggleUserLikeAction’.

This is because ApplicationDispatch expects ThunkDispatch but toogleUserLike doesn’t require redux thunk.

To fix this, I will add a workaround for now, as this error will be automatically resolved when we migrate to the user actions.

Reference Commit: https://github.com/suhas86/git-clone-react-redux/commit/aaf9aa0197d9a3216bf12903ebb990737250cdee

Alright, with a few hiccups, since we’re migrating from Redux to Redux Toolkit, we’ve set up our store. Now, let’s talk about slices. Not the pizza kind, but the function kind.

Creating a Slice 🍕

As mentioned earlier, createSlice in Redux Toolkit acts like a multitool for defining Redux actions and reducers in one shot. It automagically generates action creators and reducers from your slice reducers, cutting down on repetitive code and making your Redux setup cleaner and easier to manage.

Instead of modifying existing files, let’s create a new folder under Redux called features and add slices there. We’ll start with userSlice.

createSlice accepts an object that defines:

name: A string that specifies the slice name.initialState: The initial state value of the slice.reducers: An object where each key represents a Redux action type, and the corresponding value is a reducer function that updates the state in response to that action.extraReducers is an optional configuration in createSlice provided by Redux Toolkit. It allows you to handle actions from other slices or outside sources without defining additional action types or action creators explicitly within the slice.import { ActionReducerMapBuilder, createAsyncThunk, createSlice } from ‘@reduxjs/toolkit’;
import { Users } from ‘../types/usersTypes’;
import { fetchTopUsersBySize, fetchUserBySearch } from ‘../services’;
import { AsyncThunk, AsyncThunkConfig } from ‘@reduxjs/toolkit/dist/createAsyncThunk’;

interface UsersState {
loading: boolean;
users: Users | null;
error: string | null;
}

const initialState: UsersState = {
loading: false,
users: null,
error: null,
};

export const getTopUsersBySize = createAsyncThunk(
‘users/getTopUsers’,
async (size: number = 30): Promise<Users> => {
const users = await fetchTopUsersBySize(size);
return users;
}
);

export const getUsersBySearch = createAsyncThunk(‘users/getUsersBySearch’, async (userName: string): Promise<Users> => {
const users = await fetchUserBySearch(userName);
return users
})

// TODO: To make this generic
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleAsyncActions = (builder: ActionReducerMapBuilder<UsersState>, asyncThunk: AsyncThunk<Users, any, AsyncThunkConfig>) => {
builder
.addCase(asyncThunk.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(asyncThunk.fulfilled, (state, action) => {
state.users = action.payload;
state.loading = false;
state.error = null;
})
.addCase(asyncThunk.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message || ‘Oops something went wrong’;
});
};

const userSlice = createSlice({
name: ‘users’,
initialState,
reducers: {
toogleUserLike: (state, action) => {
const userId = action.payload;
if (!state.users) {
return; // If users data is null, return state as is
}
const updatedUsers = state.users.items.map((user) => {
if (user.id === userId) {
return {
…user,
isLiked: !user.isLiked, // Toggle the liked property
};
}
return user;
});
state.users.items = updatedUsers;
},
},
extraReducers: (builder) => {
handleAsyncActions(builder, getTopUsersBySize);
handleAsyncActions(builder, getUsersBySearch);
},
});

export const usersReducer = userSlice.reducer;
export const { toogleUserLike } = userSlice.actions;
export default userSlice;Initial State and Async Thunks Setup:We define an initial state (UsersState) that includes properties for loading status, user data, and error handling.createAsyncThunk functions (getTopUsersBySize and getUsersBySearch) are created to fetch user data asynchronously from backend services (fetchTopUsersBySize and fetchUserBySearch).

2. Handling Async Actions:

The handleAsyncActions function is defined to manage state updates for pending, fulfilled, and rejected states of async thunks. It uses ActionReducerMapBuilder from Redux Toolkit to add cases for each async action’s lifecycle events (pending, fulfilled, rejected).

3. Creating the Slice:

createSlice is used to define a slice of the Redux state named ‘users’. It includes:name: Identifies the slice in the Redux store.initialState: Sets the initial state for the ‘users’ slice.reducers: Defines additional synchronous actions (toggleUserLike) that update the state based on user interaction, such as toggling a user’s like status.

4. Integrating Async Actions with extraReducers:

The extraReducers property in createSlice integrates the async action handlers (handleAsyncActions) with the slice. This ensures that state updates for async actions (getTopUsersBySize and getUsersBySearch) are managed alongside the synchronous actions (toggleUserLike).

5. Exporting Slice Reducer and Actions:

The userSlice.reducer and userSlice.actions are exported for use in configuring the Redux store and connecting with React components, respectively.

This approach using createSlice and createAsyncThunk from Redux Toolkit simplifies Redux setup by reducing boilerplate code and enhancing readability, making it easier to manage complex state and async operations in React applications.

Now that our slice has generated action creators and reducers magically, let’s update the store to use the reducer from the slice and the home page to use action creators from the slice.

Commit reference: https://github.com/suhas86/git-clone-react-redux/commit/8f0b0fd4d857a6cfbecbcb0cc4cf11a0ab0b8f16

If you run the application, you will see our application working seamlessly with the home page using Redux Toolkit. You can also observe the actions generated by our slice.

Alright, folks, it’s cleanup time 🧹

Let’s get rid of all the unnecessary types, actions, and reducers cluttering our codebase.

Commit reference: https://github.com/suhas86/git-clone-react-redux/commit/57aa1640f7531d981735f9dbc1cdf3b63c0db4fdDeleted filesFinal code: https://github.com/suhas86/git-clone-react-redux/tree/feature/redux-toolkit

Feel free to play around with this code and challenge yourself to move the Repository page on your own.

Entire migration code: https://github.com/suhas86/git-clone-react-redux/tree/feature/redux-toolkit-final

🎉 Congratulations, folks! With this, we’ve successfully migrated the entire application to Redux Toolkit. I hope this migration article has been as smooth as butter on toast for you. Stay tuned for the bonus Part Three, where we’ll dive into using RTK Query for data fetching. Get ready to level up your Redux game!

Happy Coding!!!

Let’s migrate redux to redux toolkit: A Three-Part Series was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.

​ Level Up Coding – Medium

about Infinite Loop Digital

We support businesses by identifying requirements and helping clients integrate AI seamlessly into their operations.

Gartner
Gartner Digital Workplace Summit Generative Al

GenAI sessions:

  • 4 Use Cases for Generative AI and ChatGPT in the Digital Workplace
  • How the Power of Generative AI Will Transform Knowledge Management
  • The Perils and Promises of Microsoft 365 Copilot
  • How to Be the Generative AI Champion Your CIO and Organization Need
  • How to Shift Organizational Culture Today to Embrace Generative AI Tomorrow
  • Mitigate the Risks of Generative AI by Enhancing Your Information Governance
  • Cultivate Essential Skills for Collaborating With Artificial Intelligence
  • Ask the Expert: Microsoft 365 Copilot
  • Generative AI Across Digital Workplace Markets
10 – 11 June 2024

London, U.K.