6 AI-Generated React Bad Smells to Catch Before You Merge
AI coding tools produce React at high volume, and the bugs they introduce are not syntax errors — they are silent runtime problems that pass manual testing of a single flow. A team merging AI-generated PRs without a checklist will accumulate extra render cycles, stale form data, and broken memoization that degrade UX and inflate the performance budget with no visible cause.
AI-generated React code carries six recurring bad smells that survive code review because the syntax is flawless. The most common is stuffing derived calculations into useEffect, which adds an unnecessary render cycle and risks state drift. Copying props into useState creates a subtle bug where the form silently shows stale data after a parent re-render. Fetch calls inside useEffect without an AbortController produce race conditions that flip the UI back to old data when responses arrive out of order.
Using array index as a list key breaks input focus, internal component state, and animations whenever the list reorders. Pairing React.memo with inline objects or arrow functions makes the memoization useless while adding shallow-comparison overhead. The inverse problem — wrapping every value in useMemo and every callback in useCallback — often makes code slower than plain computation because the hook overhead exceeds the cost of the work being memoized.
A six-question checklist catches all of these in under a minute: check whether useEffect logic can live in the render phase, whether useState ever reads from props, whether async effects have cleanup, whether list keys are stable ids, whether memo components receive stable references, and whether each useMemo or useCallback actually prevents a real re-computation.
The core problem is not that AI misunderstands React syntax — it's that React best practices are decisions about an entire component tree, and AI only sees the snippet it was given. The same model that writes correct JavaScript will confidently produce a useEffect that looks idiomatic but adds a render cycle because it doesn't know the parent already handles the data.
AI's tendency to overuse hooks — stuffing things into useEffect, wrapping everything in useMemo — mirrors what a junior developer does after reading the docs but before experiencing production. The model has absorbed the API surface without the cost model.
The six patterns form two opposing failure modes: AI adds state and effects where none are needed (patterns 1 and 2), and it adds memoization that either doesn't work (pattern 5) or works but costs more than it saves (pattern 6). Both come from the same root cause — the model cannot see the runtime tradeoffs.
Race conditions in useEffect fetches are a known footgun that every data-fetching library solves, yet AI-generated code almost never uses those libraries unless explicitly prompted. This suggests the training data contains more raw fetch examples than library-adoption examples.