mrkeyoor.com_
Sun 20 Sept 11:46 UTC
npmWeb Frontendupdated 20 Sept 2026

@reduxjs/toolkit review

Redux Toolkit 2.12.0 is the official package for building a Redux store with fewer hand-written action types and immutable update helpers. `configureStore()` supplies Redux Thunk and development checks, `createSlice()` creates a reducer with matching action creators, and RTK Query adds request caching through separate entry points. The release exports RTK Query hook-option types, fixes the `isSuccess` flag while infinite queries switch cache entries, and adds a 100 ms timeout fallback when browser `requestAnimationFrame` does not flush batched updates.

Verdict

@reduxjs/toolkit 2.12.0 installed in 1.9 seconds and occupied 9 MB in our sandbox; its full import measured 14.9 KB gzipped with 0 audit findings. Install it when shared client state needs explicit transitions or RTK Query will be your single request cache, and leave local component state local.

We installed it

Lab card: what happened when we installed @reduxjs/toolkitScreenshot of @reduxjs/toolkit documentation
Install✓ · 1.9s7 packages on disk · 9 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser14.9 KBgzipped (38.9 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does @reduxjs/toolkit install cleanly?

Yes. In a fresh container with an empty cache, npm install @reduxjs/toolkit finished in 2 seconds, leaving 7 packages and 9 MB on disk. npm audit reported no known vulnerabilities.

How much does @reduxjs/toolkit add to a browser bundle?

14.9 KB gzipped (38.9 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does @reduxjs/toolkit work with both ESM and CommonJS?

Yes. Both import '@reduxjs/toolkit' and require('@reduxjs/toolkit') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does @reduxjs/toolkit include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

@reduxjs/toolkit or zustand: which should you use?

zustand: Use it for a compact hook-based client store when reducers and action history are unnecessary. @reduxjs/toolkit 2.12.0 installed in 1.9 seconds and occupied 9 MB in our sandbox; its full import measured 14.9 KB gzipped with 0 audit findings.

When should you not use @reduxjs/toolkit?

Use React state or context when data belongs to one component subtree; a global action log adds ceremony without improving ownership.

API stability4/5The central 2.x APIs remain `configureStore`, `createSlice`, `createAsyncThunk`, entity adapters, listener middleware, and RTK Query endpoint definitions. Version 2.12.0 expands exported hook-option types and uses TypeScript's native `NoInfer`; it does not replace normal store code. Stability is lower at advanced type boundaries, where upgrades can surface renamed internal types, stricter inference, or changed behavior in infinite-query status flags.
Docs5/5The official site separates tutorials, usage guides, API references, TypeScript recipes, RTK Query concepts, migration instructions, and performance guidance. Examples show store typing, generated hooks, cache tags, listener cancellation, and normalized entities in context. The volume is the main obstacle: readers can copy an API example without learning when state belongs in a component, the Redux store, or the RTK Query cache.
Maintenance5/5The unarchived repository was pushed on 2026-08-24 and showed 11,224 stars plus 289 open issues and pull requests. Release 2.12.0 shipped on 2026-05-15 with fixes for infinite-query status, background-tab batching, listener matcher types, and exported RTK Query hook options. The package also tracks current Redux, Immer, Reselect, React, and TypeScript behavior, which requires steady compatibility work.
Ecosystem5/5npm counted 27,695,780 downloads for the latest completed week. The package includes Redux, thunk support, Immer reducers, Reselect selectors, entity helpers, listener middleware, and RTK Query, while React integration comes through a declared `react-redux` peer. Bundled declarations plus working CommonJS and ESM loading cover mixed toolchains, and Redux DevTools gives the action model a mature inspection path across frameworks.

Use it if

  • Several screens need the same client state, and explicit actions plus Redux DevTools history will help the team trace changes.
  • An existing Redux application still has hand-written reducers, action constants, or manual immutable copies that can move into slices.
  • You want one store to coordinate normalized entities, async workflows, and listener effects under TypeScript.
  • RTK Query's endpoint definitions, generated React hooks, tag invalidation, and shared cache match your server-state needs.
Skip it if

Setup reality

Our Node 22 sandbox installed @reduxjs/toolkit 2.12.0 in 1.9 seconds. npm left 7 packages using 9 MB and reported 0 known vulnerabilities at every severity. The package declares 6 direct dependencies and 2 peer dependencies. It is a CommonJS package with an exports map; both require() and ESM import worked. TypeScript declarations are bundled. Our whole-package browser check produced 38.9 KB minified and 14.9 KB gzipped.

React projects also need compatible react and react-redux peers. Create the store once, mount one Provider, export RootState and AppDispatch from the configured store, and derive typed hooks from those types. The default middleware checks for mutations and non-serializable values during development. Dates, class instances, promises, and browser objects can trigger warnings; reshape them, keep them outside Redux, or configure a narrow ignored path after confirming the tradeoff.

RTK Query should usually have one API slice per base URL because each slice owns middleware, cache keys, subscriptions, and tag invalidation. Add both its reducer and middleware to the store. Cache lifetime is driven by active subscriptions and keepUnusedDataFor; endpoint calls do not become a general offline database. Authentication belongs in prepareHeaders or a custom base query, and token refresh needs explicit retry rules to avoid loops.

Immer lets slice reducers write assignment-style code, yet those reducers still describe immutable updates. Do not hold a draft and use it after the reducer returns. createAsyncThunk dispatches lifecycle actions and returns a fulfilled Redux promise even when the request action is rejected; call .unwrap() when component code must throw on failure. Version 2.12.0's batching fallback helps background tabs flush within 100 ms, but timing-sensitive UI should still observe state rather than assume immediate React rendering.

Patterns

Create the application store configure-store

import { configureStore } from '@reduxjs/toolkit'
import todosReducer from './todosSlice'

export const store = configureStore({
  reducer: { todos: todosReducer },
})

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

`configureStore()` adds thunk middleware, Redux DevTools support, and development checks by default. Export types from the actual store instead of duplicating its shape.

Define state updates and actions together create-slice

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

type CounterState = { value: number }

const counter = createSlice({
  name: 'counter',
  initialState: { value: 0 } satisfies CounterState,
  reducers: {
    added(state, action: PayloadAction<number>) {
      state.value += action.payload
    },
  },
})

export const { added } = counter.actions
export default counter.reducer

Immer tracks writes to the draft and returns immutable state. Do not mutate Redux state outside a case reducer.

Type React Redux hooks once create-typed-hooks

import { useDispatch, useSelector, useStore } from 'react-redux'
import type { AppDispatch, AppStore, RootState } from './store'

export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
export const useAppStore = useStore.withTypes<AppStore>()

The `.withTypes()` helpers keep component code tied to the configured store types and avoid repeating annotations at each call site.

Model an async request lifecycle run-async-thunk

export const fetchUser = createAsyncThunk(
  'users/fetchOne',
  async (id: string, { signal }) => {
    const response = await fetch(`/api/users/${id}`, { signal })
    if (!response.ok) throw new Error(`HTTP ${response.status}`)
    return (await response.json()) as User
  },
)

The thunk emits pending, fulfilled, and rejected actions. Pass its `signal` into cancellable work so `promise.abort()` can stop the request.

Handle a thunk failure in a component unwrap-thunk-result

try {
  const user = await dispatch(fetchUser(id)).unwrap()
  navigate(`/users/${user.id}`)
} catch (error) {
  setMessage(String(error))
}

A dispatched async thunk resolves to its final action. `.unwrap()` returns the payload or throws the rejected value so ordinary `try` and `catch` work.

Store records by ID normalize-entities

const users = createEntityAdapter<User>()
const initialState = users.getInitialState({ status: 'idle' as const })

const usersSlice = createSlice({
  name: 'users',
  initialState,
  reducers: {
    usersReceived: users.setAll,
    userUpdated: users.updateOne,
  },
})

Entity adapters manage `ids` and `entities`; they do not model relationships between entity types or fetch the records.

Derive filtered state memoize-selector

const selectTodos = (state: RootState) => state.todos.items
const selectFilter = (_state: RootState, filter: string) => filter

export const selectVisibleTodos = createSelector(
  [selectTodos, selectFilter],
  (todos, filter) => todos.filter((todo) => todo.status === filter),
)

Reselect reuses the last result when its input references are unchanged. Avoid allocating arrays inside input selectors because that defeats memoization.

Run a cancellable side effect listen-for-action

const listener = createListenerMiddleware()

listener.startListening({
  actionCreator: searchChanged,
  effect: async (action, api) => {
    api.cancelActiveListeners()
    await api.delay(300)
    api.dispatch(searchRequested(action.payload))
  },
})

Add `listener.middleware` to the store. `cancelActiveListeners()` cancels earlier instances of this listener, which is useful for debounce behavior.

Create an RTK Query API slice define-query-api

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  endpoints: (build) => ({
    getPost: build.query<Post, string>({
      query: (id) => `posts/${id}`,
    }),
  }),
})

export const { useGetPostQuery } = api

Mount `api.reducer` at `api.reducerPath` and append `api.middleware`. Missing either piece breaks caching or subscriptions.

Add a token to RTK Query requests attach-auth-header

const baseQuery = fetchBaseQuery({
  baseUrl: '/api',
  prepareHeaders(headers, { getState }) {
    const token = (getState() as RootState).auth.token
    if (token) headers.set('authorization', `Bearer ${token}`)
    return headers
  },
})

This reads the latest token for each request. Token refresh and retry limits still need a custom base-query wrapper.

Refetch a list after a mutation invalidate-cache-tags

getPosts: build.query<Post[], void>({
  query: () => 'posts',
  providesTags: [{ type: 'Post', id: 'LIST' }],
}),
addPost: build.mutation<Post, NewPost>({
  query: (body) => ({ url: 'posts', method: 'POST', body }),
  invalidatesTags: [{ type: 'Post', id: 'LIST' }],
})

Declare `tagTypes: ['Post']` on the API. Invalidation only coordinates endpoints inside that API slice.

Patch cached data with rollback optimistic-update

async onQueryStarted({ id, title }, { dispatch, queryFulfilled }) {
  const patch = dispatch(
    api.util.updateQueryData('getPost', id, (draft) => {
      draft.title = title
    }),
  )
  try {
    await queryFulfilled
  } catch {
    patch.undo()
  }
}

The endpoint name and cache argument must match an existing entry. Undo the exact patch when the request fails or invalidate its tags to fetch authoritative data.

Alternatives

PackageRegistryPick it when
zustandnpmUse it for a compact hook-based client store when reducers and action history are unnecessary.
mobxnpmUse it when observable objects and automatic dependency tracking fit the team's mental model.
jotainpmUse it when state composes naturally as independent atoms instead of one action-driven store.

More web frontend guides

postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.