Day 2: Redux Toolkit Basics
What You'll Learn Today
- How to install Redux Toolkit and React-Redux
- Creating a Store with
configureStore - Defining state, reducers, and actions with
createSlice - Building a counter app step by step
- Building a todo list app
- Using
Provider,useSelector, anduseDispatch - How Immer enables immutable updates
- Comparing legacy Redux boilerplate vs RTK
Installation
To use Redux Toolkit, you need two packages.
npm install @reduxjs/toolkit react-redux
| Package | Purpose |
|---|---|
@reduxjs/toolkit |
Redux core + utilities (createSlice, configureStore, etc.) |
react-redux |
React-Redux bindings (Provider, useSelector, useDispatch, etc.) |
configureStore: Creating the Store
configureStore is the function for creating a Redux Store. It replaces legacy Redux's createStore and automatically configures:
- Redux DevTools Extension integration
- redux-thunk middleware (for async logic)
- Development checks (mutation detection, non-serializable value detection)
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer: {
// Register slice reducers here
}
});
export default store;
TypeScript version
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer: {
// Register slice reducers here
}
});
// Infer types from the store itself
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
createSlice: Defining a Slice
createSlice is the core API of Redux Toolkit. A single function call defines all of the following:
- Initial state (initialState)
- Reducer functions (state update logic)
- Action Creators (auto-generated)
- Action Types (auto-generated)
flowchart TB
subgraph createSlice["createSlice()"]
Name["name: 'counter'"]
Initial["initialState: { value: 0 }"]
Reducers["reducers: { increment, decrement, ... }"]
end
createSlice --> Actions["Auto-generated Actions<br/>counter/increment<br/>counter/decrement"]
createSlice --> Reducer["Exported Reducer<br/>counterSlice.reducer"]
Actions --> Dispatch["Used with dispatch()"]
Reducer --> Store["Registered in configureStore"]
style createSlice fill:#3b82f6,color:#fff
style Actions fill:#f59e0b,color:#fff
style Reducer fill:#22c55e,color:#fff
Practice 1: Counter App
Let's build the simplest possible Redux application -- a counter.
Step 1: Create the Slice
// src/features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment(state) {
// Thanks to Immer, you can write "mutating" code
state.value += 1;
},
decrement(state) {
state.value -= 1;
},
incrementByAmount(state, action) {
state.value += action.payload;
},
reset(state) {
state.value = 0;
}
}
});
// Export Action Creators
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;
// Export Reducer
export default counterSlice.reducer;
TypeScript version
// src/features/counter/counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface CounterState {
value: number;
}
const initialState: CounterState = {
value: 0
};
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment(state) {
state.value += 1;
},
decrement(state) {
state.value -= 1;
},
incrementByAmount(state, action: PayloadAction<number>) {
state.value += action.payload;
},
reset(state) {
state.value = 0;
}
}
});
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;
export default counterSlice.reducer;
Step 2: Configure the Store
// src/app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
const store = configureStore({
reducer: {
counter: counterReducer
}
});
export default store;
TypeScript version
// src/app/store.ts
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
const store = configureStore({
reducer: {
counter: counterReducer
}
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
Step 3: Wrap the App with Provider
The Provider component makes the Redux Store available to the entire React component tree.
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import store from './app/store';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
Step 4: Access the Store from Components
// src/features/counter/Counter.js
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount, reset } from './counterSlice';
function Counter() {
// useSelector: Read state from the Store
const count = useSelector((state) => state.counter.value);
// useDispatch: Get the dispatch function
const dispatch = useDispatch();
return (
<div>
<h1>Counter: {count}</h1>
<div>
<button onClick={() => dispatch(increment())}>+1</button>
<button onClick={() => dispatch(decrement())}>-1</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
<button onClick={() => dispatch(incrementByAmount(10))}>+10</button>
<button onClick={() => dispatch(reset())}>Reset</button>
</div>
</div>
);
}
export default Counter;
TypeScript version
// src/features/counter/Counter.tsx
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount, reset } from './counterSlice';
import type { RootState } from '../../app/store';
function Counter() {
const count = useSelector((state: RootState) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<h1>Counter: {count}</h1>
<div>
<button onClick={() => dispatch(increment())}>+1</button>
<button onClick={() => dispatch(decrement())}>-1</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
<button onClick={() => dispatch(incrementByAmount(10))}>+10</button>
<button onClick={() => dispatch(reset())}>Reset</button>
</div>
</div>
);
}
export default Counter;
Verifying the Data Flow
Let's trace what happens when you click the "+1" button.
sequenceDiagram
participant User as User
participant UI as Counter Component
participant Dispatch as dispatch()
participant Reducer as counterSlice.reducer
participant Store as Redux Store
User->>UI: Clicks "+1" button
UI->>Dispatch: dispatch(increment())
Note over Dispatch: Action: { type: 'counter/increment' }
Dispatch->>Reducer: Process Action
Reducer->>Store: state.value = 0 + 1 = 1
Store->>UI: useSelector receives new value
UI->>User: Displays "Counter: 1"
useSelector in Depth
useSelector is the hook for reading state from the Redux Store.
Basic Usage
// Select only the part of state you need from the entire Store
const count = useSelector((state) => state.counter.value);
Key Point: Performance Optimization
useSelector uses reference equality to determine whether to re-render the component. If the selector returns the same reference as last time, the re-render is skipped.
// GOOD: Returning a primitive value (won't re-render if value is the same)
const count = useSelector((state) => state.counter.value);
const userName = useSelector((state) => state.user.name);
// CAUTION: Returning a new object causes re-render every time
// (because a new object reference is created each time)
const userData = useSelector((state) => ({
name: state.user.name,
email: state.user.email,
}));
// β This triggers a re-render every time!
// GOOD: Use separate selectors when you need multiple values
const name = useSelector((state) => state.user.name);
const email = useSelector((state) => state.user.email);
useDispatch in Depth
useDispatch returns the dispatch function for sending Actions to the Store.
const dispatch = useDispatch();
// Dispatch the return value of an Action Creator
dispatch(increment());
// This is equivalent to:
// dispatch({ type: 'counter/increment' })
dispatch(incrementByAmount(5));
// This is equivalent to:
// dispatch({ type: 'counter/incrementByAmount', payload: 5 })
Practice 2: Todo List App
Let's build a more practical example -- a todo list application.
Step 1: Create the Todo Slice
// src/features/todos/todosSlice.js
import { createSlice } from '@reduxjs/toolkit';
let nextId = 1;
const todosSlice = createSlice({
name: 'todos',
initialState: {
items: [],
filter: 'all' // 'all' | 'active' | 'completed'
},
reducers: {
addTodo(state, action) {
state.items.push({
id: nextId++,
text: action.payload,
completed: false,
createdAt: new Date().toISOString()
});
},
toggleTodo(state, action) {
const todo = state.items.find(item => item.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
deleteTodo(state, action) {
state.items = state.items.filter(item => item.id !== action.payload);
},
editTodo(state, action) {
const { id, text } = action.payload;
const todo = state.items.find(item => item.id === id);
if (todo) {
todo.text = text;
}
},
setFilter(state, action) {
state.filter = action.payload;
},
clearCompleted(state) {
state.items = state.items.filter(item => !item.completed);
},
toggleAll(state) {
const allCompleted = state.items.every(item => item.completed);
state.items.forEach(item => {
item.completed = !allCompleted;
});
}
}
});
export const {
addTodo,
toggleTodo,
deleteTodo,
editTodo,
setFilter,
clearCompleted,
toggleAll
} = todosSlice.actions;
export default todosSlice.reducer;
TypeScript version
// src/features/todos/todosSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface Todo {
id: number;
text: string;
completed: boolean;
createdAt: string;
}
type FilterType = 'all' | 'active' | 'completed';
interface TodosState {
items: Todo[];
filter: FilterType;
}
const initialState: TodosState = {
items: [],
filter: 'all'
};
let nextId = 1;
const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
addTodo(state, action: PayloadAction<string>) {
state.items.push({
id: nextId++,
text: action.payload,
completed: false,
createdAt: new Date().toISOString()
});
},
toggleTodo(state, action: PayloadAction<number>) {
const todo = state.items.find(item => item.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
deleteTodo(state, action: PayloadAction<number>) {
state.items = state.items.filter(item => item.id !== action.payload);
},
editTodo(state, action: PayloadAction<{ id: number; text: string }>) {
const { id, text } = action.payload;
const todo = state.items.find(item => item.id === id);
if (todo) {
todo.text = text;
}
},
setFilter(state, action: PayloadAction<FilterType>) {
state.filter = action.payload;
},
clearCompleted(state) {
state.items = state.items.filter(item => !item.completed);
},
toggleAll(state) {
const allCompleted = state.items.every(item => item.completed);
state.items.forEach(item => {
item.completed = !allCompleted;
});
}
}
});
export const {
addTodo,
toggleTodo,
deleteTodo,
editTodo,
setFilter,
clearCompleted,
toggleAll
} = todosSlice.actions;
export default todosSlice.reducer;
Step 2: Register in the Store
// src/app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
import todosReducer from '../features/todos/todosSlice';
const store = configureStore({
reducer: {
counter: counterReducer,
todos: todosReducer
}
});
export default store;
TypeScript version
// src/app/store.ts
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
import todosReducer from '../features/todos/todosSlice';
const store = configureStore({
reducer: {
counter: counterReducer,
todos: todosReducer
}
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
Step 3: Add Todo Input Component
// src/features/todos/AddTodo.js
import { useState } from 'react';
import { useDispatch } from 'react-redux';
import { addTodo } from './todosSlice';
function AddTodo() {
const [text, setText] = useState('');
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
dispatch(addTodo(text.trim()));
setText('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Enter a task..."
/>
<button type="submit">Add</button>
</form>
);
}
export default AddTodo;
TypeScript version
// src/features/todos/AddTodo.tsx
import { useState, FormEvent } from 'react';
import { useDispatch } from 'react-redux';
import { addTodo } from './todosSlice';
function AddTodo() {
const [text, setText] = useState('');
const dispatch = useDispatch();
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
if (text.trim()) {
dispatch(addTodo(text.trim()));
setText('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Enter a task..."
/>
<button type="submit">Add</button>
</form>
);
}
export default AddTodo;
Step 4: Todo List Display Component
// src/features/todos/TodoList.js
import { useSelector, useDispatch } from 'react-redux';
import { toggleTodo, deleteTodo, setFilter, clearCompleted, toggleAll } from './todosSlice';
function TodoList() {
const { items, filter } = useSelector((state) => state.todos);
const dispatch = useDispatch();
// Apply filter
const filteredItems = items.filter(item => {
if (filter === 'active') return !item.completed;
if (filter === 'completed') return item.completed;
return true;
});
const activeCount = items.filter(item => !item.completed).length;
const completedCount = items.filter(item => item.completed).length;
return (
<div>
{/* Filter buttons */}
<div>
<button
onClick={() => dispatch(setFilter('all'))}
style={{ fontWeight: filter === 'all' ? 'bold' : 'normal' }}
>
All ({items.length})
</button>
<button
onClick={() => dispatch(setFilter('active'))}
style={{ fontWeight: filter === 'active' ? 'bold' : 'normal' }}
>
Active ({activeCount})
</button>
<button
onClick={() => dispatch(setFilter('completed'))}
style={{ fontWeight: filter === 'completed' ? 'bold' : 'normal' }}
>
Completed ({completedCount})
</button>
</div>
{/* Bulk actions */}
<div>
<button onClick={() => dispatch(toggleAll())}>Toggle All</button>
<button onClick={() => dispatch(clearCompleted())}>Clear Completed</button>
</div>
{/* Todo list */}
<ul>
{filteredItems.map(item => (
<li key={item.id}>
<input
type="checkbox"
checked={item.completed}
onChange={() => dispatch(toggleTodo(item.id))}
/>
<span style={{
textDecoration: item.completed ? 'line-through' : 'none',
color: item.completed ? '#999' : '#000'
}}>
{item.text}
</span>
<button onClick={() => dispatch(deleteTodo(item.id))}>Delete</button>
</li>
))}
</ul>
{filteredItems.length === 0 && (
<p>No tasks found</p>
)}
</div>
);
}
export default TodoList;
TypeScript version
// src/features/todos/TodoList.tsx
import { useSelector, useDispatch } from 'react-redux';
import { toggleTodo, deleteTodo, setFilter, clearCompleted, toggleAll } from './todosSlice';
import type { RootState } from '../../app/store';
function TodoList() {
const { items, filter } = useSelector((state: RootState) => state.todos);
const dispatch = useDispatch();
const filteredItems = items.filter(item => {
if (filter === 'active') return !item.completed;
if (filter === 'completed') return item.completed;
return true;
});
const activeCount = items.filter(item => !item.completed).length;
const completedCount = items.filter(item => item.completed).length;
return (
<div>
<div>
<button
onClick={() => dispatch(setFilter('all'))}
style={{ fontWeight: filter === 'all' ? 'bold' : 'normal' }}
>
All ({items.length})
</button>
<button
onClick={() => dispatch(setFilter('active'))}
style={{ fontWeight: filter === 'active' ? 'bold' : 'normal' }}
>
Active ({activeCount})
</button>
<button
onClick={() => dispatch(setFilter('completed'))}
style={{ fontWeight: filter === 'completed' ? 'bold' : 'normal' }}
>
Completed ({completedCount})
</button>
</div>
<div>
<button onClick={() => dispatch(toggleAll())}>Toggle All</button>
<button onClick={() => dispatch(clearCompleted())}>Clear Completed</button>
</div>
<ul>
{filteredItems.map(item => (
<li key={item.id}>
<input
type="checkbox"
checked={item.completed}
onChange={() => dispatch(toggleTodo(item.id))}
/>
<span style={{
textDecoration: item.completed ? 'line-through' : 'none',
color: item.completed ? '#999' : '#000'
}}>
{item.text}
</span>
<button onClick={() => dispatch(deleteTodo(item.id))}>Delete</button>
</li>
))}
</ul>
{filteredItems.length === 0 && (
<p>No tasks found</p>
)}
</div>
);
}
export default TodoList;
Step 5: Main App Component
// src/App.js
import AddTodo from './features/todos/AddTodo';
import TodoList from './features/todos/TodoList';
function App() {
return (
<div>
<h1>Todo List</h1>
<AddTodo />
<TodoList />
</div>
);
}
export default App;
How Immer Works
The Immer library used internally by Redux Toolkit dramatically improves the Redux development experience.
Why Immer Matters
Redux requires immutable state updates. In legacy Redux, you had to manually write immutable updates using spread syntax.
// Legacy Redux: Manual immutable updates (nightmare with deep nesting)
function reducer(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
third: {
...state.first.second.third,
value: action.payload
}
}
}
};
}
// Redux Toolkit (Immer): Intuitive "mutating" syntax
function reducer(state, action) {
state.first.second.third.value = action.payload;
}
How Immer Works Under the Hood
flowchart LR
Original["Original State<br/>(Frozen Object)"]
Draft["Draft<br/>(Proxy Object)"]
New["New State<br/>(New Frozen Object)"]
Original -->|"Immer creates a Proxy"| Draft
Draft -->|"Records changes"| Draft
Draft -->|"Applies changes to<br/>produce new object"| New
style Original fill:#3b82f6,color:#fff
style Draft fill:#f59e0b,color:#fff
style New fill:#22c55e,color:#fff
- Immer creates a Proxy (Draft) of the original state
- Your reducer code "mutates" the Draft
- Immer tracks all changes and produces a new object with only the modified parts
- The original state is never actually changed
Immer Gotchas
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
// OK: Mutate state directly (Immer handles it)
addTodo(state, action) {
state.push(action.payload);
},
// OK: Return a new value (same as a regular reducer)
clearAll() {
return [];
},
// BAD: Mutating state AND returning it (will cause an error)
// badReducer(state, action) {
// state.push(action.payload);
// return state; // This is NOT allowed!
// }
}
});
Rule: Inside a reducer, either mutate the state OR return a new value -- never do both. Doing both will cause an error.
The PayloadAction Type
When using TypeScript, the PayloadAction type lets you type-check the Action payload.
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface UserState {
name: string;
email: string;
age: number;
}
const userSlice = createSlice({
name: 'user',
initialState: { name: '', email: '', age: 0 } as UserState,
reducers: {
// PayloadAction<T> specifies the payload type
setName(state, action: PayloadAction<string>) {
state.name = action.payload; // action.payload is typed as string
},
setEmail(state, action: PayloadAction<string>) {
state.email = action.payload;
},
setAge(state, action: PayloadAction<number>) {
state.age = action.payload; // action.payload is typed as number
},
updateProfile(state, action: PayloadAction<Partial<UserState>>) {
// action.payload is { name?: string, email?: string, age?: number }
Object.assign(state, action.payload);
}
}
});
Using PayloadAction also enforces correct types when dispatching.
dispatch(setName('John Doe')); // OK
dispatch(setName(123)); // TypeScript error!
dispatch(setAge(25)); // OK
dispatch(setAge('twenty-five')); // TypeScript error!
Legacy Redux vs Redux Toolkit Comparison
Let's compare the same todo app written with legacy Redux and RTK.
Legacy Redux
// actionTypes.js
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';
export const DELETE_TODO = 'DELETE_TODO';
export const SET_FILTER = 'SET_FILTER';
// actionCreators.js
import { ADD_TODO, TOGGLE_TODO, DELETE_TODO, SET_FILTER } from './actionTypes';
let nextId = 1;
export const addTodo = (text) => ({
type: ADD_TODO,
payload: { id: nextId++, text, completed: false }
});
export const toggleTodo = (id) => ({ type: TOGGLE_TODO, payload: id });
export const deleteTodo = (id) => ({ type: DELETE_TODO, payload: id });
export const setFilter = (filter) => ({ type: SET_FILTER, payload: filter });
// reducer.js
import { ADD_TODO, TOGGLE_TODO, DELETE_TODO, SET_FILTER } from './actionTypes';
const initialState = { items: [], filter: 'all' };
export default function todosReducer(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return { ...state, items: [...state.items, action.payload] };
case TOGGLE_TODO:
return {
...state,
items: state.items.map(item =>
item.id === action.payload
? { ...item, completed: !item.completed }
: item
)
};
case DELETE_TODO:
return {
...state,
items: state.items.filter(item => item.id !== action.payload)
};
case SET_FILTER:
return { ...state, filter: action.payload };
default:
return state;
}
}
// store.js
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import todosReducer from './reducer';
export default createStore(
combineReducers({ todos: todosReducer }),
applyMiddleware(thunk)
);
Files: 4, ~60 lines of code
Redux Toolkit
// todosSlice.js
import { createSlice } from '@reduxjs/toolkit';
let nextId = 1;
const todosSlice = createSlice({
name: 'todos',
initialState: { items: [], filter: 'all' },
reducers: {
addTodo(state, action) {
state.items.push({ id: nextId++, text: action.payload, completed: false });
},
toggleTodo(state, action) {
const todo = state.items.find(item => item.id === action.payload);
if (todo) todo.completed = !todo.completed;
},
deleteTodo(state, action) {
state.items = state.items.filter(item => item.id !== action.payload);
},
setFilter(state, action) {
state.filter = action.payload;
}
}
});
export const { addTodo, toggleTodo, deleteTodo, setFilter } = todosSlice.actions;
export default todosSlice.reducer;
// store.js
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from './todosSlice';
export default configureStore({
reducer: { todos: todosReducer }
});
Files: 2, ~30 lines of code
Side-by-Side Comparison
| Aspect | Legacy Redux | Redux Toolkit |
|---|---|---|
| Files needed | 4 files | 2 files |
| Lines of code | ~60 lines | ~30 lines |
| Action Types | Manually defined constants | Auto-generated |
| Action Creators | Manually defined functions | Auto-generated |
| Immutable updates | Spread syntax (manual) | Immer (automatic) |
| Middleware setup | applyMiddleware (manual) | Auto-configured |
| DevTools | Separate setup required | Auto-configured |
| Type safety | Manual type definitions | Easy with PayloadAction |
Summary
| Concept | Description |
|---|---|
configureStore |
Creates the Store with DevTools, thunk, and dev checks auto-configured |
createSlice |
Defines state, reducers, and actions in a single function call |
Provider |
React component that supplies the Store to the component tree |
useSelector |
Hook for reading state from the Store. Optimizes re-renders via reference equality |
useDispatch |
Hook that returns the dispatch function for sending Actions |
| Immer | Library enabling "mutable" syntax that produces immutable updates |
PayloadAction |
TypeScript type for Actions with typed payloads |
| Slice | A bundle of state + reducers + actions, typically organized by feature |
Today we learned the fundamental Redux Toolkit APIs and built both a counter app and a todo list app. By using createSlice and configureStore, we drastically reduced the amount of code compared to legacy Redux and improved the developer experience.
Tomorrow in Day 3, we'll learn how to combine multiple slices in a practical application and explore selector patterns.
Exercises
Exercise 1: Shopping Cart Slice
Create a shopping cart slice using createSlice with the following features:
addItem(product): Add a product to the cart (if the same product exists, increment quantity by 1)removeItem(productId): Remove a product from the cart entirelyupdateQuantity({ productId, quantity }): Change the quantity of a productclearCart(): Empty the cart
Initial state:
{
items: [], // { id, name, price, quantity }
totalItems: 0,
totalPrice: 0
}
Exercise 2: useSelector Optimization
The following code has a performance problem. Identify the issue and fix it.
function CartSummary() {
const cartData = useSelector((state) => ({
items: state.cart.items,
total: state.cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
count: state.cart.items.length,
}));
return (
<div>
<p>Items: {cartData.count}</p>
<p>Total: ${cartData.total}</p>
</div>
);
}
Exercise 3: Understanding Immer
Determine which of the following reducers will work correctly and which will not, and explain why.
const slice = createSlice({
name: 'example',
initialState: { items: [], count: 0 },
reducers: {
// A
addItem(state, action) {
state.items.push(action.payload);
state.count += 1;
},
// B
resetItems(state) {
return { ...state, items: [], count: 0 };
},
// C
badReset(state) {
state.items = [];
return state;
},
// D
replaceAll(state, action) {
return { items: action.payload, count: action.payload.length };
}
}
});