Skip to main content

Command Palette

Search for a command to run...

Common React State Bugs (and Why They Happen)

Updated
4 min read
Common React State Bugs (and Why They Happen)
A
A technical extrovert building smart contract dapps. Specialized in communicating technical blockchain concepts to the web2 maxis.

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.