Creating Reducers
Reducers are pure functions that take the current state and an action, and return the new state. They must never mutate the existing state.
Creating a Reducer
import { createReducer, on } from '@ngrx/store';
import * as UserActions from './user.actions';
export interface UserState {
users: User[];
loading: boolean;
error: string | null;
}
export const initialState: UserState = {
users: [],
loading: false,
error: null,
};
export const userReducer = createReducer(
initialState,
on(UserActions.loadUsers, state => ({
...state,
loading: true,
error: null,
})),
on(UserActions.loadUsersSuccess, (state, { users }) => ({
...state,
users,
loading: false,
})),
on(UserActions.loadUsersFailure, (state, { error }) => ({
...state,
loading: false,
error,
})),
on(UserActions.addUser, (state, { user }) => ({
...state,
users: [...state.users, user],
})),
on(UserActions.deleteUser, (state, { id }) => ({
...state,
users: state.users.filter(u => u.id !== id),
})),
);
Registering Reducers
import { provideStore } from '@ngrx/store';
import { userReducer } from './user.reducer';
export const appConfig: ApplicationConfig = {
providers: [
provideStore({ users: userReducer }),
],
};
Reducer Rules
- Pure functions — Same input always produces same output
- No side effects — No HTTP calls, no mutations
- Immutable updates — Always return new state objects
- Default case — Return current state for unknown actions
// BAD: Mutating state
state.users.push(action.user); // Mutation!
return state;
// GOOD: Immutable update
return {
...state,
users: [...state.users, action.user],
};
Immutability Patterns
Immutability ensures change detection works correctly and enables time-travel debugging.
Immutable Update Patterns
// Array: add item
users: [...state.users, newItem]
// Array: remove item
users: state.users.filter(u => u.id !== id)
// Array: update item
users: state.users.map(u => u.id === id ? { ...u, ...changes } : u)
// Object: update property
loading: true
// Nested object: update deeply
...state,
address: {
...state.address,
city: 'New City',
}
Returning the Same Reference
When nothing changes, return the same state reference. This lets selectors skip recalculation:
on(UserActions.someAction, state => {
// If nothing changes, return the same reference
if (state.users.length === 0) {
return state; // Same reference — selectors skip
}
return {
...state,
users: [...state.users, newItem],
};
});
Using Immer for Complex Updates
import { produce } from 'immer';
export const userReducer = createReducer(
initialState,
on(UserActions.updateUser, (state, { id, changes }) =>
produce(state, draft => {
const user = draft.users.find(u => u.id === id);
if (user) {
Object.assign(user, changes);
}
})
),
);
Reducer Composition
// Combine multiple reducers
export const reducers = {
users: userReducer,
posts: postReducer,
comments: commentReducer,
};
provideStore(reducers)
Quiz
1. What is a reducer in NgRx?
2. Why must reducers be immutable?
3. What should a reducer return for an unknown action?
Flashcards
Question
What is a reducer?
Click to reveal answer
Answer
Pure function (state, action) => newState. No side effects, immutable.
Question
Why immutability?
Click to reveal answer
Answer
Change detection uses reference equality. Enables time-travel debugging.
Question
What to return for unknown actions?
Click to reveal answer
Answer
The current state unchanged.
Revision Notes
Key Takeaways
- 1. Reducers are pure functions
- 2. Always return new state objects (immutable)
- 3. Return same reference when nothing changes
- 4. Use on() to handle actions
Interview Tips
- • Explain why immutability matters
- • Be ready to describe reducer composition
- • Mention Immer for complex nested updates
Cheat Sheet
Reducers Cheat Sheet
Create reducer:
export const userReducer = createReducer(
initialState,
on(UserActions.loadUsers, state => ({ ...state, loading: true })),
on(UserActions.loadUsersSuccess, (state, { users }) => ({
...state,
users,
loading: false,
})),
);
Immutable updates:
// Add: [...state.items, newItem]
// Remove: state.items.filter(i => i.id !== id)
// Update: state.items.map(i => i.id === id ? { ...i, ...changes } : i)
Register:
provideStore({ users: userReducer })