React Anti-Patterns That Cause Real Maintenance Problems

Avoid the React anti-patterns that quietly raise maintenance costs: redundant state, Effect-based syncing, mutation, index keys, and overused context and memoization.

8 min read

React anti-patterns are patterns that look convenient at first and quietly raise maintenance costs later. Most of them share one trait: they store or sync data that React could derive, or they hide data flow instead of making it explicit.

Store only what cannot be derived

The most common anti-pattern is redundant state. A component stores both the inputs and the value that can be calculated from them, then keeps them aligned with an Effect.

Instead, derive the value during render. Only the two names below are state; the full name is a calculation.

App.jsxApp.jsx
import { useState } from "react";
 
function Form() {
  const [firstName, setFirstName] = useState("Taylor");
  const [lastName, setLastName] = useState("Swift");
 
  const fullName = firstName + " " + lastName;
 
  return <p>Hello, {fullName}.</p>;
}

There is no second render pass and no chance for the stored value to drift out of sync. The full rules are in Derived State in React: What Not to Store.

Do not sync state with Effects

A related anti-pattern uses useEffect to copy one state value into another whenever the first changes. That triggers an extra render and turns a simple calculation into a chain of updates.

If the second value can be computed during render, compute it. If the two values are genuinely independent but must change together, move both updates into the same event handler. The decision tree is covered in You Might Not Need an Effect: Better React Patterns.

Never mutate props or state

Mutating a prop or a state value breaks React's model, because props and state are read-only snapshots. Pushing into a props array, or assigning a field on a state object, produces bugs that only appear under Strict Mode or when another component reads the same value.

Update arrays and objects immutably. A new array or a new object signals to React that something changed, and it keeps rendering predictable. Calling a setter with a new array built from the old one is correct, while pushing onto the old array and passing it back is not.

Choose stable list keys

Using the array index as a key works only for a static list that never reorders, inserts, or deletes. The moment an item moves, React reuses the wrong DOM node and the state attached to it jumps to the wrong row.

Derive keys from stable data such as an id. Use the index only when the list is truly static and you have no stable identifier. When a list supports sorting or filtering, an index key also preserves the wrong component state, so a checked row keeps its check after moving.

Do not put everything in context

Context is for values read by many distant components, not a shortcut to avoid passing any prop. A component that puts its entire form state in context makes the data flow implicit and every consumer re-render on any change.

Pass props first, compose with children, and reach for context only when those fail. The tradeoff is laid out in Context vs Props.

Do not memoize by habit

Wrapping every value in useMemo and every callback in useCallback adds noise and identity checks without a measured problem. Memoization only pays off when a render is actually expensive, or when a stable reference is required to prevent a real re-render cascade.

Measure before optimizing. The React Compiler can automate some of this work, so manual memoization is often unnecessary in new code. When a component genuinely re-renders too often, memoization may be the fix, but only after measurement shows it is needed.

When to stop and refactor

A component needs attention when you see redundant state, an Effect copying state, direct mutation, index keys on a dynamic list, or a context that owns everything. Pick the smallest fix, apply it, and re-check the render behavior before moving on. Fixing one anti-pattern at a time keeps the diff reviewable, which matters because these bugs are often subtle.

Rune AI

Rune AI

Key Insights

  • Store only what cannot be derived during render.
  • Do not use Effects to keep two state values in sync.
  • Treat props and state as read-only snapshots.
  • Use stable keys derived from data, not array indexes.
  • Reach for context and memoization only when the problem calls for them.
RunePowered by Rune AI

Frequently Asked Questions

Are all these patterns always wrong?

No. Some have narrow legitimate uses, such as index keys for a static list that never reorders. The problem is reaching for them by default, which is where maintenance costs appear.

How do I know if my component has redundant state?

Ask whether each state value can be calculated from other props or state during render. If it can, it is redundant and should be derived instead of stored.

Conclusion

The worst React anti-patterns store or sync data that could be derived, mutate values React treats as read-only, or hide data flow. Replace each one with the simplest explicit alternative and your app becomes easier to change.