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.
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
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.
Frequently Asked Questions
Are all these patterns always wrong?
How do I know if my component has redundant state?
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.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.