How would you implement Redux Toolkit in a React application?
RTK collapses Redux boilerplate: `createSlice` (reducers + actions in one place with Immer), `configureStore` (middleware + devtools wired), `createAsyncThunk` (async lifecycle), `createEntityAdapter` (normalized CRUD), RTK Query (React-Query-like data layer over Redux). Reduces boilerplate ~70% vs hand-rolled Redux.
Pieces
createSlice
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [] as Item[] },
reducers: {
add(state, action: PayloadAction<Item>) {
state.items.push(action.payload); // looks mutable — Immer makes it immutable under the hood
},
remove(state, action: PayloadAction<string>) {
state.items = state.items.filter((i) => i.id !== action.payload);
},
},
});
export const { add, remove } = cartSlice.actions;
export default cartSlice.reducer;- Auto-generates action creators.
- Reducer accepts mutable-looking code (Immer produces a new immutable state).
- Action types are namespaced (
cart/add).
configureStore
import { configureStore } from '@reduxjs/toolkit';
export const store = configureStore({
reducer: { cart: cartSlice.reducer, profile: profileSlice.reducer },
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;Sets up:
- Middleware (thunk, immutability check, serializability check).
- Redux DevTools.
- Hot-reloading-friendly setup.
Typed hooks
import { useDispatch, useSelector } from 'react-redux';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;createAsyncThunk
export const fetchProfile = createAsyncThunk('profile/fetch', async (id: string) => {
const res = await api.profile(id);
return res.data;
});
const slice = createSlice({
name: 'profile',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchProfile.pending, (s) => { s.status = 'loading'; })
.addCase(fetchProfile.fulfilled, (s, a) => { s.status = 'done'; s.data = a.payload; })
.addCase(fetchProfile.rejected, (s, a) => { s.status = 'error'; s.error = a.error.message; });
},
});Three action types per thunk: pending, fulfilled, rejected. Pattern for async lifecycle.
createEntityAdapter
const adapter = createEntityAdapter<Post>({ sortComparer: (a, b) => b.createdAt - a.createdAt });
const initialState = adapter.getInitialState({ status: 'idle' });
const slice = createSlice({
name: 'posts',
initialState,
reducers: {
added: adapter.addOne,
upserted: adapter.upsertOne,
removed: adapter.removeOne,
},
});
// Selectors
const { selectAll, selectById } = adapter.getSelectors((state: RootState) => state.posts);Normalized state ({ ids: [], entities: {} }) with CRUD reducers — saves a lot of boilerplate.
RTK Query
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['Post'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => 'posts',
providesTags: ['Post'],
}),
addPost: build.mutation<Post, NewPost>({
query: (body) => ({ url: 'posts', method: 'POST', body }),
invalidatesTags: ['Post'],
}),
}),
});
export const { useGetPostsQuery, useAddPostMutation } = api;A full data-fetching layer with caching, invalidation tags, optimistic updates, polling, RTK-DevTools integration.
Why people use it
- Less boilerplate than hand-rolled Redux.
- Built-in DevTools, middleware, types.
- RTK Query competes with React Query inside the Redux ecosystem.
- Strict action discipline still preserved.
Vs hooks/Zustand
- RTK has more ceremony but more structure.
- For large teams or complex client domains, the discipline is worth it.
- For most app shapes, hooks + React Query + Zustand is lighter.
Interview framing
"createSlice defines state + reducers + actions in one place; Immer makes the mutable-looking code produce immutable state. configureStore wires middleware (thunk, immutability check) and DevTools. createAsyncThunk standardizes async lifecycle into pending/fulfilled/rejected. createEntityAdapter normalizes collections with CRUD reducers + selectors. RTK Query is a full data layer over Redux — caches with tag-based invalidation, like React Query but in the Redux store. RTK collapses about 70% of Redux boilerplate and is the recommended way to use Redux today."
Follow-up questions
- •Compare RTK Query and React Query.
- •When would you use createEntityAdapter?
- •What's the role of Immer in createSlice?
Common mistakes
- •Mixing legacy Redux with RTK in one app.
- •Mutating state outside reducers.
- •Using RTK Query and React Query side by side (pick one).
Performance considerations
- •Selector memoization matters; reselect or RTK's createSelector helps.
Edge cases
- •Non-serializable values in actions (Dates, class instances).
- •Listener middleware vs sagas.
- •RTK Query cache eviction tuning.
Real-world examples
- •Most enterprise React apps using Redux today.