Common React State Bugs (and Why They Happen)

TL;DR
Most bugs in React apps are not logic errors; they come from how state is structured and updated.
When state is:
duplicated
derived but stored
updated unpredictably
The UI eventually becomes inconsistent.
1. Why These Bugs Persist
React does not enforce how state is modeled. Incorrect patterns still appear to work until the UI grows in complexity.
As more components depend on the same data:
Inconsistencies surface
Updates become harder to reason about
Edge cases multiply
These are structural problems, not syntax issues.
2. Common State Bugs
2.1 Stale Closures
setCount(count + 1);
setCount(count + 1);
Both updates capture the same value of count, so the result is incorrect.
Correct Approach
setCount(c => c + 1);
setCount(c => c + 1);
Functional updates ensure each change uses the latest state.
2.2 Storing Derived State
const [tasks, setTasks] = useState([]);
const [completedTasks, setCompletedTasks] = useState([]);
completedTasks depends entirely on tasks. Storing both creates two sources of truth.
Correct Approach
const completedTasks = tasks.filter(t => t.completed);
Compute derived values during render instead of storing them.
2.3 Direct State Mutation
tasks.push(newTask);
setTasks(tasks);
React relies on reference changes to detect updates. Mutating state in place breaks that mechanism.
Correct Approach
setTasks(prev => [...prev, newTask]);
Always produce a new reference when updating state.
2.4 Incorrect useEffect Dependencies
Missing Dependency
useEffect(() => {
fetchData(userId);
}, []);
The effect never re-runs when userId changes, leading to stale data.
Infinite Loop
useEffect(() => {
setData(process(data));
}, [data]);
Updating a dependency inside the effect causes it to re-trigger indefinitely.
Practical Rule
Effects should synchronize external data or side effects, not continuously transform state.
2.5 UI and State Out of Sync
{isOpen && <Modal />}
If the modal is hidden via DOM or CSS without updating isOpen, the UI and state diverge.
This leads to:
Broken reopen logic
Inconsistent UI behavior
State must be the single driver of UI.
2.6 Multiple Sources of Truth
const [items, setItems] = useState([]);
const [selectedItems, setSelectedItems] = useState([]);
When selectedItems depends on items, maintaining both introduces synchronization risk.
Correct Approach
const selectedItems = items.filter(i => i.selected);
Keep a single authoritative state and derive everything else.
3. Quick Reference
| Issue | Cause | Fix |
|---|---|---|
| Stale closures | Using outdated values | Functional updates |
| Derived state bugs | Storing computed data | Compute on render |
| Mutation | In-place updates | Immutable updates |
| useEffect bugs | Wrong dependencies | Align dependencies with data flow |
| UI mismatch | UI not driven by state | Make state authoritative |
| Duplicate state | Multiple sources of truth | Consolidate state |
4. Preventing These Bugs
Most of these issues disappear when state is structured correctly:
Keep one source of truth
Avoid storing derived values
Use functional updates when state depends on previous values
Scope state to the smallest necessary boundary
Keep transitions explicit and predictable
Final Takeaway
React does not break - state models do.
When state is:
minimal
centralized where necessary
and derived correctly
The UI becomes predictable and easier to reason about.
Good state management is less about fixing bugs and more about preventing them through structure.



