State Management Mistakes That Make React Apps Hard to Maintain

Avoid the state management mistakes that make React apps hard to maintain, from derived and duplicate state to mutating state and syncing it with Effects.

7 min read

State management mistakes compound quietly. A component starts simple, then duplicated values, impossible states, and deep objects make every change risky. This guide lists the mistakes that make React apps hard to maintain and the smaller pattern that fixes each.

The fixes are mostly structural: keep one source of truth per value, update it immutably, and compute the rest.

Storing values you can derive

A derived value is one you can calculate from props or other state during render. Storing it means updating the copy every time the source changes, and the two fall out of sync the moment you forget.

App.jsxApp.jsx
const [fullName, setFullName] = useState("");
useEffect(() => {
  setFullName(firstName + " " + lastName);
}, [firstName, lastName]);

The Effect runs after render, so the component paints once with a stale value, then again with the new one. The fix is a plain variable computed during render, with no state and no Effect. The pattern is covered in derived state in React.

Contradictory state

Two booleans that should never both be true leave room for an impossible state. The classic case is a form that tracks isSending and isSent separately.

App.jsxApp.jsx
const [isSending, setIsSending] = useState(false);
const [isSent, setIsSent] = useState(false);

A missed update leaves both true at once. Replace them with one status value that can be typing, sending, or sent, and derive the booleans when you need them. Deriving the booleans from a status string also removes the class of bug where one update is forgotten.

Mirroring props into state

Initializing state from a prop only copies the first value. Later prop changes are ignored, so the two drift apart.

App.jsxApp.jsx
function Message({ messageColor }) {
  const [color, setColor] = useState(messageColor);
}

Use the prop directly instead. Mirror into state only when you truly want to ignore later updates, and name the prop with an initial prefix to make that intent clear. When the prop is the source of truth, the component can never disagree with its parent.

Duplicating data across state

Holding the same object in two places means every edit must update both. Store the id and find the rest during render.

App.jsxApp.jsx
const [items, setItems] = useState(initialItems);
const [selectedItem, setSelectedItem] = useState(items[0]);

The fix keeps selectedId in state and finds selectedItem from items. Editing an item then updates the list and the selection together, because there is only one copy of the data.

Mutating state in place

React compares state by reference. Pushing into an existing array and calling the setter with the same reference looks like no change.

App.jsxApp.jsx
function addTodo(todo) {
  todos.push(todo);
  setTodos(todos);
}

Return a new array with spread or concat, and replace nested objects instead of editing them. A new reference is what tells React the value changed, so every update must produce one. Immutable updates are covered in how to update objects in React state.

Syncing state with an Effect

Using an Effect to copy state into another state variable restarts the render cycle and hides the real dependency.

App.jsxApp.jsx
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
  setVisibleTodos(getVisibleTodos(todos, filter));
}, [todos, filter]);

Compute visibleTodos during render instead. The Effect is only for synchronizing with an external system, not for deriving one piece of state from another.

Server cache in a client store

Server data has its own lifecycle: loading, caching, refetching, and errors. A client store holds a copy that nothing keeps in sync with the server.

Keep server data in a query library such as TanStack Query, and leave client stores for values only the user changes. The boundary is separate from scope questions like how to avoid global state when local state is enough.

A checkable summary

  • Store the source, derive the rest.
  • One status value instead of contradictory booleans.
  • Prefer ids over duplicated objects.
  • Never mutate state in place.
  • No Effects just to sync state.
  • Server data in a query library, client data in React state or a store.

Each mistake is fixed by the same idea: keep one source of truth per value and let everything else compute from it.

Rune AI

Rune AI

Key Insights

  • Compute derived values during render instead of storing them.
  • Replace contradictory booleans with one status value.
  • Never mutate arrays or objects in place.
  • Keep server cache in a query library, not a client store.
RunePowered by Rune AI

Frequently Asked Questions

What is redundant state in React?

State you can calculate from props or other state during render. Store the source value and compute the rest instead of keeping two copies in sync.

Why is mutating state a mistake?

React compares state by reference, so a mutation in place looks like no change and the component may not re-render. Replace arrays and objects immutably.

Should server data live in a client store?

No. Server data needs caching, refetching, and invalidation. A query library like TanStack Query handles that lifecycle better than a client store.

Conclusion

Most state management mistakes come from storing what can be derived, duplicating data, mutating in place, or reaching for Effects to sync state. Keep one source of truth per value and update it immutably.