100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
React

Common React Pitfalls

Frequent React mistakes — direct state mutation, bad dependency arrays, index keys, and more — with fixes.

Interview PrepIntermediate13 min readJul 8, 2026
Analogies

Overview

Most React bugs come from a small set of recurring mistakes rather than exotic edge cases. Mutating state directly, misusing the useEffect dependency array, using unstable list keys, and calling hooks conditionally are patterns that work 'most of the time' in a demo but break in subtle ways under real-world conditions like re-renders, list reordering, or fast user interaction. Recognizing these pitfalls — and knowing precisely why each one is wrong, not just that it's discouraged — is one of the fastest ways to level up as a React developer and is a favorite area for interviewers to probe.

🏏

Cricket analogy: Like a bowler whose slower ball works fine in a casual net session but gets smashed in a high-pressure T20 final against sharp batsmen who exploit its subtle tell — knowing exactly why it fails separates club players from pros.

Frequently Asked Questions

Q: Why is mutating state directly (e.g. pushing into a state array) a problem in React?

React decides whether to re-render by comparing the previous and next state references, typically with Object.is. If you mutate an array or object in place (state.items.push(newItem)) and then call setState(state), the reference is unchanged, so React sees the 'same' object and may skip the re-render entirely, or worse, update the DOM inconsistently since some optimizations assume immutability. The fix is to always create a new array or object — setItems([...items, newItem]) or setState({ ...state, key: value }) — so React can detect the change via reference comparison and correctly schedule a re-render.

🏏

Cricket analogy: Like an umpire who only signals a decision change when a completely new replay angle is submitted — if you hand back the same tape after just scribbling a note on it, the umpire assumes nothing changed and never reviews it again.

Q: What goes wrong when a useEffect is missing a dependency it actually uses?

When an effect reads a variable, like a piece of state or a prop, but that variable isn't listed in the dependency array, the effect's closure captures the value from whichever render it was created in and never sees updates to it — this is the classic 'stale closure' bug. For example, an interval callback that reads a count state variable but has an empty dependency array will keep incrementing from the same stale count value forever instead of the current one. The fix is either to include every reactive value the effect reads in the dependency array, or to restructure the code — for instance using the functional updater form setCount(c => c + 1) — so the effect no longer needs that value as a dependency.

🏏

Cricket analogy: Like a commentator who memorized the score at the start of the innings and keeps repeating that same number over and over on air, never checking the live scoreboard again — the fix is to either keep glancing at the board or say 'one more than whatever it currently is.'

Q: Why is using array index as a key dangerous when list order can change?

Index-based keys tie a list item's identity to its position rather than its actual data, so if items are reordered, inserted, or removed, React can incorrectly match old elements (and their component state, like open/closed toggles or input values) to different underlying data than before. A common symptom is a text input in a to-do list retaining the wrong text after an earlier item is deleted, because React reused the DOM node at that index for a different todo. The fix is to key list items by a stable, unique identifier from the data itself, such as a database id or a generated UUID, so identity survives reordering.

🏏

Cricket analogy: Like assigning a player's identity by their batting-order slot instead of their name — if the opener gets injured and a substitute steps into slot one, the scoreboard keeps showing the injured player's stats attached to that slot instead of following the actual player.

Q: How do inline object or function props cause unnecessary re-renders?

Every time a parent component renders, an inline object literal (style={{ color: 'red' }}) or arrow function (onClick={() => doThing(id)}) passed as a prop creates a brand-new reference, even if its contents are identical to the last render. If the child component is wrapped in React.memo to skip re-renders when props are unchanged, that optimization is defeated because the shallow prop comparison sees a new object/function reference every time and re-renders anyway. The fix is to memoize the value with useMemo for objects or useCallback for functions when passing them to a memoized child, or to move static objects outside the component entirely so the reference never changes.

🏏

Cricket analogy: Like a coach who reprints a brand-new fielding-position card before every over even when the field hasn't changed — the umpire, trained to only re-check when the card looks different, ends up re-verifying it needlessly; the fix is to reuse the same card when nothing has actually changed.

Q: What happens if you forget a cleanup function in useEffect for subscriptions or timers?

Effects that set up a subscription, event listener, interval, or timeout need to tear that resource down when the component unmounts or before the effect re-runs, by returning a cleanup function from useEffect. Without cleanup, the subscription or timer keeps running after the component is gone, which can cause memory leaks, duplicated event listeners piling up on every re-run, or a 'setState on an unmounted component' warning when the stale callback eventually fires and tries to update state that no longer exists. The fix is to always return a cleanup function — return () => clearInterval(id) or return () => subscription.unsubscribe() — that mirrors whatever setup the effect performed.

🏏

Cricket analogy: Like a groundskeeper who starts the sprinkler system before every match but never turns it off when the match ends — the fix is for whoever starts the sprinkler to also be responsible for shutting it off once play concludes, mirroring the setup exactly.

Q: Why can't hooks be called inside conditionals, loops, or after an early return?

React identifies each hook call by its position in the sequence of hook calls during a render, not by name, so it depends on that sequence being identical on every render for a given component. If a hook is called only when some condition is true, the total number and order of hook calls can differ between renders, causing React to associate the wrong stored state or effect with the wrong hook call — a bug that often manifests as state 'bleeding' between unrelated pieces of data or as a hard runtime error. The fix is to always call the hook unconditionally at the top level and instead put the conditional logic inside the hook, such as useEffect(() => { if (condition) { ... } }, [condition]).

🏏

Cricket analogy: Like a scorer who records each ball strictly by its position in the over — first ball, second, third — and if a ball is occasionally skipped from the tally depending on some condition, every later ball's recorded number shifts and gets attributed to the wrong delivery.

Q: What's the problem with fetching data directly in the component body instead of inside useEffect?

Calling a fetch or other side-effecting operation directly during render means it runs on every single render, including re-renders triggered by unrelated state changes, and it runs during the render phase itself which React expects to be pure and free of side effects. This can trigger infinite loops if the fetch's response updates state, which triggers a re-render, which triggers the fetch again. The fix is to perform data fetching inside useEffect with an appropriate dependency array so it runs only when the relevant inputs actually change, and to handle race conditions (e.g., a fast-changing search query) with cleanup logic that ignores stale responses.

🏏

Cricket analogy: Like a commentator who re-requests the entire player database from the stats server every single time they speak, even for unrelated remarks, instead of only fetching fresh stats when the batsman actually changes — this can flood the server and even trigger a loop if each fetch causes another update.

Q: Why does directly mutating props inside a child component cause problems?

Props are meant to be treated as read-only inputs owned by the parent; mutating a prop object or array inside a child silently changes data the parent still believes is unchanged, since the parent's reference points to the same mutated object. This breaks React's reference-equality assumptions the same way direct state mutation does, and it can produce inconsistent behavior where the UI doesn't reflect the actual current data, or where the parent's own re-render logic misses the change because the reference never changed. The fix is for a child that needs to modify data to call a callback function passed down as a prop so the parent can update its own state immutably, keeping the one-way data flow intact.

🏏

Cricket analogy: Like a fielding coach handed the official team-sheet by the captain only to read positions off it, but who instead scribbles changes directly onto that same sheet — the captain, still holding the same sheet, has no idea it was altered; the coach should instead radio suggested changes back to the captain to update officially.

Quick Reference

  • Never mutate state directly — always create new arrays/objects so React detects the reference change.
  • Missing useEffect dependencies cause stale closures; include every reactive value the effect reads.
  • Avoid array index as key when list order can change; use a stable unique id instead.
  • Inline object/function props defeat React.memo by creating a new reference on every render.
  • Always return a cleanup function from useEffect for subscriptions, listeners, and timers.
  • Hooks must be called unconditionally at the top level, in the same order, on every render.
  • Data fetching belongs inside useEffect (or a dedicated data-fetching library), not in the render body.
  • Treat props as strictly read-only; use callbacks to let children request parent state changes.
  • The functional updater form of setState (setX(prev => ...)) avoids some stale-closure issues without extra dependencies.
  • Most React bugs trace back to a broken assumption about reference equality or hook call order.

Key Takeaways

  • Immutability isn't a style preference in React — it's how the library detects changes.
  • Every pitfall in this list traces back to either broken reference equality or an inconsistent hook call sequence.
  • Cleanup functions and correct dependency arrays prevent the most common memory-leak and stale-data bugs.
  • When something 'usually works but occasionally breaks,' suspect a reference-identity or ordering issue first.

Practice what you learned

Was this page helpful?

Topics covered

#React#ReactJsStudyNotes#WebDevelopment#CommonReactPitfalls#Common#Pitfalls#Frequently#Asked#StudyNotes#SkillVeris