You inherit a React codebase. Or worse you wrote it six months ago.
Either way, you open the project and see the same story: prop drilling three levels deep, useEffect hooks with missing dependencies, components that re-render for no reason, and API calls scattered across the app with no error handling.
This isn't a "bad developer" problem. It's a React codebase maintenance problem, and almost every team hits it as the app grows.
Manually auditing a large codebase for performance issues, bad patterns, and hidden bugs takes days. So I tried something different: I pointed Cursor AI, an AI-powered code editor built on top of VS Code, at a real React project and asked it to review the code.
In this article, you'll learn:
- The 7 real issues Cursor AI found and fixed in a React codebase
- The actual before/after code for each fix
- Why these problems happen in the first place
- Common mistakes React developers make with AI-assisted refactoring
- Best practices to keep your codebase clean going forward
If you're a React developer looking to improve code quality with AI tools, this is a practical, no-fluff walkthrough — not a marketing pitch for Cursor.
Why React Codebases Get Messy (The Real Reason)
Before jumping into fixes, it's worth understanding why this happens.
React gives you flexibility, not structure. There's no built-in rule for where state should live, how components should be split, or how side effects should be managed.
So under deadline pressure, developers usually:
- Copy-paste components instead of abstracting shared logic
- Add
useEffectwithout thinking about dependency arrays - Pass props through multiple layers instead of using context or state managers
- Skip memoization because "it works fine locally"
- Forget cleanup functions in effects that involve subscriptions or timers
None of these are catastrophic on their own. But they compound. After a few months, the codebase becomes fragile, slow, and hard to onboard new developers onto.
This is exactly the kind of pattern-recognition problem AI code review tools are good at — they don't get tired, and they scan the entire file tree consistently.
Solution Overview: Using Cursor AI as a Code Reviewer
Cursor AI lets you chat with your codebase, ask it to review specific files, and generate fixes directly in context — it understands imports, component relationships, and project structure, not just isolated snippets.
The workflow is simple:
- Open the codebase in Cursor
- Select a file or folder
- Ask Cursor to review it for bugs, performance issues, or anti-patterns
- Review the suggested diff
- Accept, reject, or modify the change
It's not magic — it's pattern matching at scale, backed by a large language model that has seen millions of React codebases. Let's go through what it actually caught.
Step-by-Step: How I Reviewed My React Codebase with Cursor
Step 1: Setup
Install Cursor from cursor.sh and open your existing React project folder directly — no migration needed since it's a VS Code fork.
# Cursor uses your existing project as-is
cd my-react-app
cursor .
Step 2: Configuration
Enable "Codebase Indexing" in Cursor settings so it can reason about relationships between files instead of just the open tab.
Settings → Features → Codebase Indexing → Enable
This step matters. Without indexing, Cursor only sees the file you have open, which limits its ability to catch cross-component issues like prop drilling.
Step 3: Core Review
Open the chat panel (Cmd/Ctrl + L) and ask targeted questions instead of vague ones.
Good prompt:
Review this component for unnecessary re-renders, missing
dependency arrays, and prop drilling. Suggest fixes with code.
Vague prompt (avoid this):
Is this code good?
Specific prompts produce specific, actionable diffs.
Step 4: Final Integration
Apply fixes one file at a time and run your test suite after each change. Don't accept a batch of AI-generated diffs across the whole app at once — review incrementally.
The 7 Things Cursor AI Fixed in My React Codebase
1. Missing useEffect Dependencies
Before:
useEffect(() => {
fetchUserData(userId);
}, []); // userId missing from deps
After (Cursor's fix):
useEffect(() => {
fetchUserData(userId);
}, [userId]);
Why it matters: the original code only fetched data once, even if userId changed later — a classic stale closure bug that's hard to spot manually.
2. Unnecessary Re-renders from Inline Functions
Before:
<Button onClick={() => handleClick(item.id)} />
After:
const handleItemClick = useCallback(
(id) => handleClick(id),
[handleClick]
);
<Button onClick={() => handleItemClick(item.id)} />
Why it matters: inline functions create a new reference on every render, breaking React.memo optimizations on child components.
3. Prop Drilling Through Multiple Components
Before:
<Dashboard user={user}>
<Sidebar user={user}>
<UserBadge user={user} />
</Sidebar>
</Dashboard>
After (Context introduced):
const UserContext = createContext(null);
function Dashboard() {
return (
<UserContext.Provider value={user}>
<Sidebar />
</UserContext.Provider>
);
}
function UserBadge() {
const user = useContext(UserContext);
return <span>{user.name}</span>;
}
Why it matters: props were passed through components that didn't even use them, just to reach a deeply nested child.
4. Unhandled Promise Rejections in API Calls
Before:
const fetchData = async () => {
const res = await fetch("/api/data");
const json = await res.json();
setData(json);
};
After:
const fetchData = async () => {
try {
const res = await fetch("/api/data");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
setData(json);
} catch (error) {
console.error("Failed to fetch data:", error);
setError(error.message);
}
};
Why it matters: a failed request was silently swallowed, leaving the UI stuck in a loading state with no error feedback.
5. Missing Cleanup Functions
Before:
useEffect(() => {
const interval = setInterval(() => refreshToken(), 60000);
}, []);
After:
useEffect(() => {
const interval = setInterval(() => refreshToken(), 60000);
return () => clearInterval(interval);
}, []);
Why it matters: without cleanup, the interval kept running after the component unmounted, causing memory leaks and duplicate token refresh calls.
6. Duplicated Logic Across Components
Cursor flagged three separate components with near-identical form validation logic and suggested extracting a custom hook.
function useFormValidation(initialValues, validate) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const handleChange = (e) => {
setValues({ ...values, [e.target.name]: e.target.value });
};
const validateForm = () => {
const newErrors = validate(values);
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
return { values, errors, handleChange, validateForm };
}
Why it matters: three copies of the same logic meant three places to fix a bug instead of one.
7. Non-Memoized Expensive Computations
Before:
const sortedItems = items.sort((a, b) => a.price - b.price);
After:
const sortedItems = useMemo(
() => [...items].sort((a, b) => a.price - b.price),
[items]
);
Why it matters: the original code re-sorted on every render and mutated the original array in place — a double bug that's easy to miss in review.
Common Mistakes Developers Make (Even With AI Help)
- Blindly accepting every AI suggestion without understanding why the change was made
- Applying fixes across the whole codebase at once, making it hard to trace a regression
- Skipping tests after refactors, assuming AI-generated code is automatically correct
- Not indexing the codebase first, which limits Cursor to single-file context
- Treating AI review as a replacement for code review, not a supplement to it
AI tools are excellent at pattern recognition, but they don't know your business logic or edge cases. Always review the "why," not just the diff.
Best Practices for Using AI Code Review Tools
- Review one file or feature at a time, not the entire repo in one pass
- Always run your existing test suite after applying suggested fixes
- Use specific, technical prompts — vague prompts produce vague fixes
- Combine AI review with ESLint rules like
react-hooks/exhaustive-depsfor continuous enforcement - Keep a changelog of AI-suggested refactors so your team can audit decisions later
Visual Explanation (What to Look At)
(Screenshots recommended here for a published version)
- Screenshot 1: Cursor's chat panel showing a code review prompt and the suggested diff side-by-side
-
Screenshot 2: Before/after folder structure showing the new
hooks/directory after extractinguseFormValidation -
Screenshot 3: React DevTools Profiler showing reduced re-render count after the
useCallbackanduseMemofixes
Real-World Use Case: Where This Matters Most
These exact issues show up constantly in:
- SaaS dashboards — where prop drilling and re-renders slow down data-heavy tables
- Admin panels — where forms are duplicated across multiple entity types
- Mobile-first React apps — where memory leaks from missing cleanup functions drain battery and performance
- Production systems with API-heavy UIs — where unhandled promise rejections cause silent failures in customer-facing screens
If your app fits any of these categories, it's worth running an AI-assisted review pass at least once per quarter.
Conclusion
Cursor AI didn't rewrite my architecture or replace my judgment as a developer. What it did was catch 7 specific, common React problems — missing dependencies, unnecessary re-renders, prop drilling, unhandled errors, missing cleanup, duplicated logic, and non-memoized computations — faster than a manual audit would have.
These aren't exotic bugs. They're the kind of small issues that quietly accumulate in every growing React codebase, and catching them early keeps your app fast, maintainable, and easier to onboard new developers onto.
If you're maintaining a React app of any real size, an AI-assisted review pass is a low-effort, high-value habit worth building into your workflow.
Want to Build React Apps Faster with AI?
Learning React is one thing. Building real-world applications efficiently is another.
You can use ChatGPT, Claude, and Cursor to write code, debug issues, refactor components, generate features, and speed up your development but getting useful results depends heavily on how you prompt AI.
That’s why I created The Ultimate React + Cursor Prompt Library (1000+ AI Prompts) a practical collection of AI prompts designed specifically for React developers.
Inside the library, you’ll find 1,000+ practical prompts covering React development, UI components, debugging, refactoring, performance optimization, API integration, state management, testing, architecture, and more.
Each prompt is designed to help you get better results from AI coding assistants like Cursor, ChatGPT, and Claude, so you can spend less time figuring out what to ask and more time building.
Instead of staring at a blank Cursor chat wondering what prompt to write, you can start with proven prompts and adapt them to your own projects.
Whether you’re building a SaaS, freelance project, startup, dashboard, or personal application, this library can help you code faster, solve problems quicker, and get more out of AI-assisted development.
👉 The Ultimate React + Cursor Prompt Library: 1000+ AI Prompts →
Top comments (0)