
September 12, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
State Management with Redux Toolkit solves a problem every growing React front end hits: props drill through five layers, duplicate fetch logic in three components, and race conditions when two tabs update the same cart. Classic Redux fixed predictability but punished you with ceremony. Custom software projects that pair a Laravel or Symfony API with a React dashboard still need a disciplined client store when screens share auth, filters, and cached lists. Redux Toolkit (RTK) is the official, opinionated layer on top of Redux. It ships createSlice, Immer-powered reducers, async thunks, and RTK Query for data fetching. This guide walks through a store you can ship in production—not a toy counter app.
configureStore, colocated slices with createSlice, and optional RTK Query for server cache—replacing hand-written action types and reducers while keeping predictable, debuggable global state.What Is State Management with Redux Toolkit and When Do You Need It?
Redux Toolkit is the recommended way to write Redux logic in 2026. The core Redux library still provides the store, subscriptions, and middleware pipeline. RTK removes the repetitive parts.
You reach for global state when multiple distant components read or write the same data. Auth session, shopping cart, UI theme, and cross-page filters are typical cases. Local useState stays fine for form fields and toggles that never leave one component.
On full-stack builds I deliver, the split is familiar. Server truth lives in Laravel 13 or a REST API. The React shell holds session tokens, optimistic UI flags, and cached list pages. That boundary keeps your backend authoritative while the front end stays responsive. If you mainly work in Vue, the mental model parallels Pinia versus Vuex 4—colocated stores, less boilerplate, DevTools support.
Skip Redux when your app is mostly static pages or a thin wrapper around server-rendered HTML. A brochure site built with WordPress development rarely needs a client store. Skip it when React Context plus useReducer covers two or three shared values and no complex async flows exist.
How Do You Set Up a Redux Toolkit Store in a React Project?
Start with a current React toolchain. Node.js 26 LTS and npm 12 are sensible defaults in 2026. Vite 8.x scaffolds faster than legacy Create React App.
Install dependencies
npm create vite@latest my-dashboard -- --template react-ts
cd my-dashboard
npm install @reduxjs/toolkit react-redux
npm install Create the store entry point
Centralize configuration in src/app/store.ts. One file exports the store type and hooks.
import { configureStore } from '@reduxjs/toolkit';
import authReducer from '../features/auth/authSlice';
import cartReducer from '../features/cart/cartSlice';
export const store = configureStore({
reducer: {
auth: authReducer,
cart: cartReducer,
},
devTools: import.meta.env.DEV,
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch; Wire the Provider
Wrap your root component once. Every child can then use typed hooks.
import { Provider } from 'react-redux';
import { store } from './app/store';
createRoot(document.getElementById('root')!).render(
<Provider store={store}>
<App />
</Provider>
); Export typed hooks
Never import raw useDispatch and useSelector all over the codebase. Wrap them.
import { useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from '../app/store';
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>(); That pattern gives autocomplete for slice names and action payloads. It catches typos at compile time instead of in production.
- Run the Vite scaffold with TypeScript.
- Add
@reduxjs/toolkitandreact-redux. - Define
configureStorewith feature reducers. - Wrap the tree in
<Provider>. - Expose typed hooks and use them everywhere.
How Do createSlice and createAsyncThunk Handle State Logic?
createSlice generates action creators and a reducer from a name, initial state, and reducer functions. Immer runs under the hood. You can write mutating-looking code that stays immutable.
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
type CartItem = { id: string; qty: number };
type CartState = {
items: CartItem[];
status: 'idle' | 'loading' | 'failed';
};
const initialState: CartState = { items: [], status: 'idle' };
const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addItem(state, action: PayloadAction<CartItem>) {
const existing = state.items.find(i => i.id === action.payload.id);
if (existing) {
existing.qty += action.payload.qty;
} else {
state.items.push(action.payload);
}
},
clearCart(state) {
state.items = [];
},
},
});
export const { addItem, clearCart } = cartSlice.actions;
export default cartSlice.reducer; Async work belongs in createAsyncThunk or RTK Query. Thunks fit custom orchestration—retry logic, multi-step flows, or writing to several slices after one response.
import { createAsyncThunk } from '@reduxjs/toolkit';
export const fetchProfile = createAsyncThunk(
'auth/fetchProfile',
async (_, { rejectWithValue }) => {
const res = await fetch('/api/me', { credentials: 'include' });
if (!res.ok) return rejectWithValue(await res.text());
return res.json();
}
); Handle lifecycle states in extraReducers. Pending, fulfilled, and rejected cases stay colocated with the slice they mutate.
A common mistake is stuffing fetch calls inside components. Move them to thunks or RTK Query endpoints. Components should dispatch intent, not manage HTTP details.
Should You Use RTK Query Instead of Manual Fetch Logic?
RTK Query ships with Redux Toolkit. It generates hooks for queries and mutations. It caches responses, deduplicates in-flight requests, and refetches on focus or interval when you configure it.
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: '/api',
prepareHeaders: (headers, { getState }) => {
const token = (getState() as RootState).auth.token;
if (token) headers.set('Authorization', `Bearer ${token}`);
},
}),
tagTypes: ['Order', 'Product'],
endpoints: (builder) => ({
getOrders: builder.query<Order[], void>({
query: () => 'orders',
providesTags: ['Order'],
}),
createOrder: builder.mutation<Order, Partial<Order>>({
query: (body) => ({ url: 'orders', method: 'POST', body }),
invalidatesTags: ['Order'],
}),
}),
});
export const { useGetOrdersQuery, useCreateOrderMutation } = api; Register the API reducer and middleware in the store.
export const store = configureStore({
reducer: {
auth: authReducer,
[api.reducerPath]: api.reducer,
},
middleware: (getDefault) => getDefault().concat(api.middleware),
}); For eCommerce dashboards—order lists, inventory panels, fulfilment boards—RTK Query saves weeks of cache bookkeeping. I have seen the same pattern on Laravel eCommerce admin panels where the API is stable and list endpoints dominate the UI.
| Approach | Best for | Trade-off |
|---|---|---|
| Local useState | Single-component UI state | No shared access across routes |
| React Context | Theme, locale, simple auth flag | Re-renders grow with consumer count |
| createSlice + thunks | Custom async flows, multi-slice updates | You own cache invalidation |
| RTK Query | CRUD against REST or GraphQL | Learning curve for tags and transforms |
| Redux Toolkit full store | Large apps, DevTools, time-travel debug | Bundle size and setup overhead |
The official Redux docs recommend RTK Query for server state and slices for client-only state. That split keeps your store readable six months later.
How Do You Structure Folders and Test Redux Toolkit Code?
Organize by feature, not by technical type. A features/cart/ folder holds the slice, selectors, components, and tests. Avoid a global reducers/ dump that nobody owns.
- src/app/ — store, root hooks, global types.
- src/features/<name>/ — slice, API endpoints, UI tied to that domain.
- src/shared/ — dumb components and utilities with no store imports.
Selectors belong in the feature folder. Memoized selectors via createSelector prevent unnecessary re-renders when unrelated slice fields change.
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from '../../app/store';
const selectCart = (state: RootState) => state.cart.items;
export const selectCartTotal = createSelector(selectCart, (items) =>
items.reduce((sum, item) => sum + item.qty * item.price, 0)
); Testing slices and thunks
Test reducers as pure functions. Dispatch actions and assert the next state. No DOM required.
import cartReducer, { addItem } from './cartSlice';
test('addItem increments quantity for existing product', () => {
const state = cartReducer(
{ items: [{ id: '1', qty: 2, price: 10 }], status: 'idle' },
addItem({ id: '1', qty: 3, price: 10 })
);
expect(state.items[0].qty).toBe(5);
}); Mock fetch for thunk tests. Assert pending and fulfilled action sequences. For RTK Query, use the library's test utilities or MSW to stub HTTP at the network boundary.
Pair unit tests with integration checks before release. A pass through testing and optimization catches selector regressions that unit files miss. Use the JSON formatter when debugging API payloads returned into your slices.
What Production Pitfalls Break Redux Toolkit Apps?
Non-serializable values in state trigger development warnings from the default middleware. Dates, class instances, and DOM nodes belong outside the store—or convert them at the boundary.
Storing entire API responses without normalization duplicates nested entities. When one product name changes, three list views show three different strings. Normalize by ID in the slice or let RTK Query handle entity adapters.
Over-using global state is the silent killer. Not every modal open flag needs Redux. Reach for local state first. Promote to a slice only when a second route or distant component genuinely needs the same value.
Bundle size matters on mobile networks in Nepal and elsewhere. Code-split routes with React.lazy. Import only the hooks you use from RTK Query generated APIs. Tree-shaking helps, but lazy routes help more on admin dashboards with dozens of screens.
Keep secrets out of the store. JWT refresh tokens in memory are acceptable for SPAs. Never persist tokens to localStorage without threat modelling XSS. Pair front-end auth with a hardened API layer—patterns I apply on API development projects using Sanctum or Passport on Laravel.
Reference the official guides when upgrading major versions. The Redux Toolkit usage guide and React state management docs stay current with recommended patterns. Redux DevTools remain essential for tracing action order during bug hunts.
If your product mixes React admin UI with a PHP monolith, treat the store as a thin client cache. Business rules stay on the server. That mirrors how I structure enterprise applications where Laravel validates every mutation regardless of what the UI optimistically shows.
Key Takeaways
- Use
configureStore, typed hooks, and feature folders as your default State Management with Redux Toolkit baseline. - Keep server data in RTK Query and client-only UI state in slices—do not merge both into one blob.
- Colocate
createAsyncThunklifecycle handlers inextraReducersinstead of scattering fetch logic in components. - Normalize entities by ID and memoize selectors to avoid stale lists and excess re-renders.
- Test reducers as pure functions; stub HTTP for thunks and RTK Query endpoints.
- Reach for local state or Context first—promote to Redux only when sharing or DevTools debugging truly justify the overhead.
People Also Ask
Is Redux Toolkit still worth using in 2026?
Yes, for medium and large React apps that share complex client state or need RTK Query's cached data layer. Small apps and mostly static sites should stay with local state or Context. RTK remains the documented standard for Redux projects and integrates cleanly with TypeScript and Vite 8.x toolchains.
What is the difference between Redux and Redux Toolkit?
Redux is the core library—store, reducers, middleware. Redux Toolkit wraps it with createSlice, configureStore, Immer, default middleware, and RTK Query. You write less boilerplate and follow opinionated defaults that prevent common misconfiguration.
Can Redux Toolkit work with Laravel or Symfony backends?
Absolutely. The store is front-end only. It consumes REST or GraphQL endpoints from Laravel 13, Symfony 8.1, or any JSON API. Use cookies or bearer tokens in prepareHeaders, and keep authorization enforcement on the server.
Does Redux Toolkit replace React Context?
Not entirely. Context still fits theme, locale, and simple providers that rarely change. Redux Toolkit wins when many components subscribe to evolving data, you need middleware for async work, or DevTools time-travel debugging saves hours during incident response.
Ship Predictable Client State on Your Next React Build
State Management with Redux Toolkit earns its place when your React front end outgrows prop drilling and duplicated fetch code. Start with a typed store, feature slices, and RTK Query for list endpoints. Add thunks only where orchestration demands it. Keep business rules on the API. Test reducers early. Your future self—and the next developer on the project—will thank you on the first production bug.
Need a React dashboard wired to a Laravel API, payment gateway, or multi-role admin portal? Review the portfolio for shipped work, explore web development services, or contact us to discuss architecture before you commit to the wrong state layer.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

