Unlocking Redux toolkit implementation

Engineering Manager
August 17, 2024
Updated on November 10, 2024
0 MIN READ
#cicd#frontend#unlocking#redux

Unlocking Redux Toolkit Implementation

Introduction

Redux has long been a cornerstone of state management in React applications, but its verbosity and boilerplate code have often been pain points for developers. Enter Redux Toolkit (RTK), the official, opinionated toolset that simplifies Redux logic while retaining its core principles.

In this guide, we’ll explore how to implement Redux Toolkit effectively, covering key concepts like slices, the configureStore API, and RTK Query. Whether you're migrating from traditional Redux or starting fresh, this post will help you unlock the full potential of RTK while reducing boilerplate and improving maintainability.


Why Redux Toolkit?

Before diving into implementation, let’s understand why RTK is the preferred choice for modern Redux applications:

  1. Less Boilerplate: RTK eliminates repetitive Redux setup code (e.g., action types, action creators, and reducers).
  2. Immutability Helpers: Uses immer under the hood, allowing you to write mutable-like code in reducers.
  3. Built-in Best Practices: Encourages patterns like normalized state and middleware (e.g., redux-thunk included by default).
  4. RTK Query: A powerful data-fetching and caching solution built into RTK.

If you're still manually writing action types and switch-case reducers, RTK will feel like a breath of fresh air.


Setting Up Redux Toolkit

Installation

First, install the required packages:

npm install @reduxjs/toolkit react-redux

Creating a Store

Instead of manually combining reducers and applying middleware, RTK provides configureStore:

import { configureStore } from '@reduxjs/toolkit'; import counterReducer from './features/counterSlice'; export const store = configureStore({ reducer: { counter: counterReducer, }, }); // Infer the `RootState` and `AppDispatch` types from the store itself export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch;

Key advantages of configureStore:

  • Automatically includes redux-thunk and Redux DevTools.
  • Enables immer for immutable updates.
  • Simplifies store configuration with sensible defaults.

Providing the Store to React

Wrap your app with the Provider from react-redux:

import { Provider } from 'react-redux'; import { store } from './app/store'; function App() { return ( <Provider store={store}> {/* Your app components */} </Provider> ); }

Creating Slices

A slice is a collection of Redux reducer logic and actions for a single feature. RTK’s createSlice auto-generates action creators and action types.

Example: Counter Slice

import { createSlice, PayloadAction } from '@reduxjs/toolkit'; interface CounterState { value: number; } const initialState: CounterState = { value: 0, }; export const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment: (state) => { state.value += 1; // Thanks to immer, this is immutable! }, decrement: (state) => { state.value -= 1; }, incrementByAmount: (state, action: PayloadAction<number>) => { state.value += action.payload; }, }, }); // Auto-generated action creators export const { increment, decrement, incrementByAmount } = counterSlice.actions; export default counterSlice.reducer;

Using the Slice in Components

With the slice created, you can dispatch actions and read state using hooks like useSelector and useDispatch:

import { useSelector, useDispatch } from 'react-redux'; import { increment, decrement } from './features/counterSlice'; import type { RootState } from './app/store'; function Counter() { const count = useSelector((state: RootState) => state.counter.value); const dispatch = useDispatch(); return ( <div> <button onClick={() => dispatch(decrement())}>-</button> <span>{count}</span> <button onClick={() => dispatch(increment())}>+</button> </div> ); }

Advanced Features: RTK Query

For data fetching and caching, RTK includes RTK Query, a powerful alternative to libraries like react-query.

Defining an API Slice

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; interface Post { id: number; title: string; body: string; } export const apiSlice = createApi({ reducerPath: 'api', baseQuery: fetchBaseQuery({ baseUrl: 'https://jsonplaceholder.typicode.com' }), endpoints: (builder) => ({ getPosts: builder.query<Post[], void>({ query: () => '/posts', }), }), }); export const { useGetPostsQuery } = apiSlice;

Integrating RTK Query with the Store

Add the API slice’s reducer and middleware to your store:

import { configureStore } from '@reduxjs/toolkit'; import { apiSlice } from './features/apiSlice'; export const store = configureStore({ reducer: { [apiSlice.reducerPath]: apiSlice.reducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(apiSlice.middleware), });

Using the Query Hook in Components

import { useGetPostsQuery } from './features/apiSlice'; function PostsList() { const { data, error, isLoading } = useGetPostsQuery(); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error loading posts</div>; return ( <ul> {data?.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); }

Conclusion

Redux Toolkit revolutionizes Redux development by eliminating boilerplate, enforcing best practices, and providing powerful tools like RTK Query. By adopting slices, configureStore, and RTK Query, you can:

  • Reduce code complexity with auto-generated actions and reducers.
  • Simplify async logic with built-in data fetching and caching.
  • Improve maintainability with a standardized, opinionated approach.

If you haven’t tried RTK yet, now’s the time! Migrate incrementally or start fresh—either way, your Redux codebase will thank you.

For further reading, check out the official Redux Toolkit documentation. Happy coding! 🚀

Share this article