How Redux Toolkit Changed Redux for the Better

Redux has long been a staple of state management in the React ecosystem. Powerful and predictable, it provided a structured way to handle complex state in large applications. But let’s be honest, writing Redux code used to feel like boilerplate hell. Between action types, action creators, switch statements, and reducers, setting up even the most basic flow felt like overkill.

Enter Redux Toolkit (RTK) the official, recommended way to write Redux logic. Since its release, RTK has fundamentally changed how developers approach Redux, addressing long-standing pain points while retaining the core strengths that made Redux popular in the first place.

In this blog, I’ll break down how Redux Toolkit has transformed Redux development for the better.

Goodbye Boilerplate, Hello Simplicity

One of the biggest complaints about "vanilla" Redux was the sheer amount of boilerplate needed to manage even small pieces of state.

Before RTK:

// Action types 
const INCREMENT = 'INCREMENT';  
// Action creators 
const increment = () => ({ type: INCREMENT });  
// Reducer 
function counterReducer(state = 0, action) {   
  switch (action.type) {     
    case INCREMENT:      
      return state + 1;     
    default:       
      return state;   
  } 
} 

With RTK:

import { createSlice } from '@reduxjs/toolkit';  

const counterSlice = createSlice(
  {  
    name: 'counter',  
    initialState: 0,   
    reducers: {     
      increment: (state) => state + 1  
    }
  });  

export const { increment } = counterSlice.actions; 
export default counterSlice.reducer;

What’s improved:

  • No more manual action types
  • Action creators auto-generated
  • Reducers co-located and clean

 

2. Mutability That Isn’t

Traditionally, Redux enforces immutability, meaning you must return new copies of state objects rather than mutate them. RTK, however, uses Immer under the hood, allowing you to write mutating code that is actually immutable.

Example:

reducers: {   
  addTodo: (state, action) => {     
    state.todos.push(action.payload); 
    // Looks like mutation, but isn't  
  } 
}

What’s improved:

  • Cleaner, more intuitive code
  • No need to spread state manually

 

3. Standardised Store Setup

Setting up a Redux store used to involve a lot of moving parts: combineReducers, applying middleware manually, and remembering the right dev-tools extension config.

With RTK:

import { configureStore } from '@reduxjs/toolkit';  
const store = configureStore({  
  reducer: {     
    counter: counterSlice.reducer  
  } 
}); 

What’s improved:

  • DevTools enabled by default
  • Thunk middleware included automatically
  • Cleaner, more secure defaults

 

4. Built-in Support for Async Logic

Redux Toolkit provides createAsyncThunk for handling async operations like API calls in a consistent, boilerplate-free way.

Example:

export const fetchUsers = createAsyncThunk(   
  'users/fetch', async () => {     
    const response = await fetch('/api/users');     
    return response.json();   
  }
);

What’s improved:

  • Eliminates need for separate action types
  • Handles loading, success, and error states automatically
  • Great for standardising async flows

 

5. Scalable Structure by Default

Redux Toolkit encourages a feature-first (a.k.a. "slice-first") architecture, where logic is grouped by feature rather than by type (e.g., actions vs reducers).

This naturally leads to:

  • Better encapsulation
  • Easier testing
  • Cleaner scaling as your app grows

 

6. Improved Developer Experience

From auto-generated action creators to better TypeScript support, RTK has made Redux much more developer-friendly. The learning curve is gentler, and the maintenance burden is lower — especially for new team members joining a project.

 

7. Data Fetching Reimagined with RTK Query

Beyond the basics, Redux Toolkit includes an incredibly powerful tool: RTK Query. It's a data-fetching and caching layer built on top of Redux and it completely changes how we think about async data in front-end apps.

Instead of manually dispatching thunks, tracking loading states, and caching results, RTK Query handles it all for you declaratively and efficiently.

✅ Key Features of RTK Query:

a) Automatic Caching & Re-fetching

RTK Query automatically caches API responses, reuses them when possible, and invalidates them when needed.

const api = createApi({   
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),   
  endpoints: (builder) => ({     
    getPosts: builder.query({       
      query: () => 'posts',     
    }),   
  }), 
});
export const { useGetPostsQuery } = api; 
  • Fetching and caching handled behind the scenes
  • Refetching controlled via configuration or triggers
  • Great for performance and reducing API load

b) Infinite Queries (Pagination Support)

Fetching paginated data (e.g. endless scroll or load more) used to be messy and custom. RTK Query supports pagination and infinite queries out of the box using parameters and merge logic.

getPosts: builder.query({  
  query: ({ page = 1 }) => `posts?page=${page}`,   
  serializeQueryArgs: ({ endpointName }) => endpointName,   
  merge: (currentCache, newItems) => {     
    currentCache.push(...newItems);   
  },  
  forceRefetch: ({ currentArg, previousArg }) =>     
    currentArg?.page !== previousArg?.page, 
}) 
  • Keep appending results to cache
  • Clean logic to support infinite scrolling

c) Streaming Updates (WebSockets / SSE)

RTK Query isn’t limited to traditional HTTP requests. It supports streaming data sources like WebSockets or Server-Sent Events (SSE), letting you listen for live updates and push them directly into the Redux store.

This enables real-time apps (e.g. chats, dashboards, trading UIs) to stay in sync effortlessly.

d) Optimistic Updates & Rollbacks

Need a snappy UX? RTK Query supports optimistic updates, allowing you to update the UI before a server response arrives and roll back gracefully if something fails.

This is huge for things like toggling likes, adding comments, or deleting items.

updatePost: builder.mutation({  
  query: (data) => ({     
    url: `/posts/${data.id}`,     
    method: 'PUT',     
    body: data,   
  }),   
  onQueryStarted: async (arg, { dispatch, queryFulfilled }) => {     
    const patchResult = dispatch(       
      api.util.updateQueryData('getPosts', undefined, (draft) => {         
        const post = draft.find(p => p.id === arg.id);         
        if (post) post.title = arg.title;      
      })     
    );     
    try {       
      await queryFulfilled;    
    } catch {       
      patchResult.undo();
      // Rollback if it fails    
    }  
  }, 
})

 

Final Thoughts

Redux used to be a love-hate relationship for many. Powerful, yes, but also verbose and finicky. Redux Toolkit has flipped that equation: you get all the power of Redux, with a modern, streamlined API that feels great to work with.

Whether you’re building a small app or architecting a large enterprise system, RTK lets you scale state management without losing your mind.

If you gave up on Redux a few years ago it might be time to take another look.

 

Back to Blog