- Understand the Redux Toolkit (RTK) mental model: slices, reducers, actions, store
- Use
createSliceand appreciate how Immer enables mutation-style reducer code - Wire the Redux store to React with
<Provider>andconfigureStore - Write typed hooks (
useAppSelector,useAppDispatch) for type-safe store access - Compare Redux Toolkit with Zustand (the previous challenge)
- Inspect state changes with Redux DevTools
The TaskFlow app already has all its UI working (with Zustand in start/).
Your job is to replace Zustand with Redux Toolkit without changing any visible behaviour.
Three slices of global client state to migrate:
| Slice | State | Actions |
|---|---|---|
sidebar |
isCollapsed: boolean |
toggle, collapse, expand |
filters |
statusFilter, sortOrder |
setStatusFilter, setSortOrder |
recentlyViewed |
projectIds: string[] |
addRecentlyViewed |
File: src/store/redux/sidebarSlice.ts
The state interface and stub reducers are already there. Fill in the three reducers:
toggle— flipstate.isCollapsedcollapse— setstate.isCollapsed = trueexpand— setstate.isCollapsed = false
Export sidebarReducer and the individual action creators (toggle, collapse, expand).
Immer is built into
createSlice— you can writestate.isCollapsed = !state.isCollapseddirectly inside a reducer. No spread needed.
File: src/store/redux/filtersSlice.ts
Fill in the two reducers:
setStatusFilter(state, action)— replacestate.statusFilterwithaction.payloadsetSortOrder(state, action)— replacestate.sortOrderwithaction.payload
Export filtersReducer and the action creators.
File: src/store/redux/recentlyViewedSlice.ts
Requirements:
- State:
{ projectIds: string[] }(initially empty) - One reducer:
addRecentlyViewed(state, action: PayloadAction<string>)- Prepend the new ID
- Remove any existing occurrence of that ID (no duplicates)
- Keep only the last 5 entries
// Hint — one-liner that satisfies all three requirements:
state.projectIds = [action.payload, ...state.projectIds.filter(id => id !== action.payload)].slice(0, 5);Export recentlyViewedReducer and addRecentlyViewed.
File: src/store/redux/store.ts
Register the three reducers with configureStore:
reducer: {
sidebar: sidebarReducer,
filters: filtersReducer,
recentlyViewed: recentlyViewedReducer,
}Export RootState and AppDispatch type aliases.
The app currently uses Zustand selectors imported from src/store/selectors.ts.
Update the following components to use useAppSelector / useAppDispatch instead:
| Component | Was using | Now uses |
|---|---|---|
Layout.tsx |
useSidebarState() |
useAppSelector + useAppDispatch |
Header.tsx |
useNotificationPrefs() |
keep as local state (notification prefs are not in Redux) |
ProjectsLayout.tsx |
useFilterPreferences() |
useAppSelector + useAppDispatch |
ProjectDetailPanel.tsx |
useAppStore(s => s.addRecentlyViewed) |
useAppDispatch + addRecentlyViewed |
Wrap the app with <Provider store={store}> in src/App.tsx.
- Install the Redux DevTools Extension in Chrome
- Open DevTools → Redux tab
- Click filter buttons and toggle the sidebar
- Use the time-travel controls to jump between past states
src/store/redux/sidebarSlice.ts ← fill in TODOs
src/store/redux/filtersSlice.ts ← fill in TODOs
src/store/redux/recentlyViewedSlice.ts ← create from scratch
src/store/redux/store.ts ← register reducers
src/store/redux/hooks.ts ← already done for you
src/App.tsx ← wrap with <Provider>
src/components/Layout.tsx ← use Redux hooks
src/pages/ProjectsLayout.tsx ← use Redux hooks
src/pages/ProjectDetailPanel.tsx ← use Redux hooks
The Zustand store (src/store/useAppStore.ts, src/store/selectors.ts) is left in
place in start/ so you can compare the two approaches side by side.
cd start # or solution
npm install
npx msw init public/ --save
npm run dev