React: Use 'Excess Property Checks' in setState() to avoid subtle bugs
Source: Dev.to
The problem
type State = {
name: string,
count: number,
}
// Initial state
const [state, setState] = useState({ name: 'Unknown', count: 0 });
// Intended update
function resetCount() {
setState(state => ({
...state,
count: 0,
}));
}
// Oops – typo goes unnoticed
setState(state => ({
...state,
cont: 0, // typo of `count`
}));
Because the spread operator produces a fully‑formed State object, the misspelled cont is treated as an excess property and the type checker does not raise an error. This can happen silently during renames (e.g., changing count to personCount) and may introduce subtle bugs.
Fix: annotate the return type of the updater
By explicitly declaring the return type of the function passed to setState, excess property checks are enforced.
type State = {
name: string,
personCount: number, // renamed from `count`
};
const [state, setState] = useState({ name: 'Unknown', personCount: 0 });
function resetCount() {
setState((state): State => ({
...state,
count: 0, // ❌ TypeScript error: 'count' does not exist in type 'State'
}));
}
Now the typo (or outdated property name) is caught at build time.
Alternative approaches
Use useReducer for complex state
Explicitly typing the reducer’s return value also triggers excess property checks.
// ❌ Inferred return type – no excess‑property checking
function reducer(state: State, action: Action) {
/* ... */
}
// ✅ Explicit return type – catches incorrect properties
function reducer(state: State, action: Action): State {
/* ... */
}
Leverage Immer for mutable‑style updates
Immer provides a proxy that lets you write “mutating” code while preserving immutability. TypeScript still validates property names.
Note: This isn’t the idiomatic React pattern and may reduce readability for some teams.
Use Partial for helper updates
// Helper that merges a partial state update
function update(partial: Partial) {
setState(s => ({
...s,
...partial,
}));
}
function resetCount() {
update({ cont: 0 }); // ❌ Error: 'cont' does not exist in type 'State'
}
Further reading
- Type Compatibility documentation – explains the design decisions behind excess property checks.