Chapter 2: React State Management with Redux
Chapter 2 of 15
Chapter 2: React State Management with Redux
2.1 Redux Fundamentals
Redux is a predictable state container for JavaScript apps. It helps manage application state in a centralized store.
Core Concepts:
- Store: Single source of truth for application state
- Actions: Plain objects describing what happened
- Reducers: Pure functions that specify how state updates
- Dispatch: Method to send actions to the store
// Action
const increment = () => ({
type: 'INCREMENT'
});
const addTodo = (text) => ({
type: 'ADD_TODO',
payload: { text, id: Date.now() }
});
// Reducer
function counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
}
// Store
import { createStore } from 'redux';
const store = createStore(counterReducer);
// Usage
store.dispatch(increment());
console.log(store.getState()); // { count: 1 }
2.2 Redux Toolkit
Redux Toolkit (RTK) is the official, opinionated way to write Redux logic. It simplifies Redux development.
import { createSlice, configureStore } from '@reduxjs/toolkit';
// Create slice
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1; // Immer allows direct mutation
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
// Export actions
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// Configure store
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
// Usage in React
import { useSelector, useDispatch } from 'react-redux';
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
</div>
);
}
2.3 Async Actions with Redux Thunk
// Async thunk
import { createAsyncThunk } from '@reduxjs/toolkit';
export const fetchUser = createAsyncThunk(
'users/fetchUser',
async (userId) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
);
// In slice
const usersSlice = createSlice({
name: 'users',
initialState: { user: null, loading: false, error: null },
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.user = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
}
});