Debugging React Apps with Cursor: Tools, Tips, and Workflows

Debugging React applications can be challenging due to component hierarchies, state management complexity, and asynchronous data flows. Cursor's integrated debugging tools and AI assistance make identifying and fixing issues faster. This guide covers React-specific debugging workflows.
Prerequisites
Before starting:
- Cursor installed (latest version)
- React project set up
- React DevTools browser extension installed (optional but recommended)
Setting Up Debugging in Cursor
Launch Configuration
Create .vscode/launch.json for React debugging:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug React App",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:///src/*": "${webRoot}/src/*"
}
},
{
"name": "Debug Next.js",
"type": "node",
"request": "launch",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"env": {
"NODE_OPTIONS": "--inspect"
},
"console": "integratedTerminal"
}
]
}
Source Maps
Ensure source maps are enabled in your build config:
// vite.config.ts
export default defineConfig({
build: {
sourcemap: true,
},
});
// next.config.js
const nextConfig = {
productionBrowserSourceMaps: true,
};
module.exports = nextConfig;
Debugging React Components
Breakpoints in JSX
Set breakpoints directly in your component files:
// src/components/UserProfile.tsx
import { useState, useEffect } from 'react';
export function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUser() {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
// Set breakpoint here
const data = await response.json();
setUser(data);
setLoading(false);
}
fetchUser();
}, [userId]);
if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
Conditional Breakpoints
Right-click on a breakpoint to set conditions:
Condition: userId === "12345"
The breakpoint only triggers when the condition is met.
State Debugging Techniques
Console Logging with Context
Use Cursor's AI to add strategic logs:
Add console.log statements to trace the state changes in this component
Cursor adds:
useEffect(() => {
console.log('[UserProfile] userId changed:', userId);
async function fetchUser() {
console.log('[UserProfile] fetching user...');
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
console.log('[UserProfile] received data:', data);
setUser(data);
}
fetchUser();
}, [userId]);
React DevTools Integration
While React DevTools runs in the browser, you can reference component data in Cursor:
- Open React DevTools in your browser
- Inspect the component tree
- Copy the component props/state
- Paste into Cursor chat for analysis
This component is re-rendering infinitely. Here are the props and state from DevTools:
[paste data]
What's causing the loop?
Hooks Troubleshooting
useEffect Dependencies
Common issue: missing or incorrect dependency arrays.
// Before - buggy
useEffect(() => {
fetchData(query);
}, []); // Missing dependency
// After - fixed
useEffect(() => {
fetchData(query);
}, [query]); // Correct dependency
Ask Cursor to audit your hooks:
Review all useEffect hooks in this file for missing dependencies
Custom Hook Debugging
// src/hooks/useLocalStorage.ts
import { useState, useEffect, useCallback } from 'react';
export function useLocalStorage<T>(key: string, initialValue: T) {
// Get stored value
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(`Error reading localStorage key "${key}":`, error);
return initialValue;
}
});
// Return a wrapped version of useState's setter
const setValue = useCallback(
(value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error);
}
},
[key, storedValue]
);
return [storedValue, setValue] as const;
}
Cursor can detect common hooks violations like calling hooks conditionally or inside loops. Use the linter integration to catch these early.
Next.js Debugging
Server-Side Debugging
Debug API routes and server components:
{
"name": "Debug Next.js Server",
"type": "node",
"request": "launch",
"runtimeExecutable": "npx",
"runtimeArgs": ["next", "dev"],
"env": {
"NODE_OPTIONS": "--inspect=9230"
},
"port": 9230
}
API Route Breakpoints
// app/api/users/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
// Set breakpoint here
const users = await prisma.user.findMany();
// Inspect users in debugger
return NextResponse.json({ users });
}
Client-Side vs Server-Side
| Context | Debugging Approach |
|---|---|
| Client Components | Chrome DevTools, React DevTools |
| Server Components | Node.js debugger in Cursor |
| API Routes | Node.js debugger |
| Middleware | Console logs + Node.js debugger |
Common React Issues and Fixes
Infinite Re-renders
Symptom: Browser freezes, console shows repeated logs.
// Problem: new object reference on every render
function Parent() {
const [count, setCount] = useState(0);
return (
<Child config={{ limit: 10 }} /> // New object every render!
);
}
// Solution: memoize the config
function Parent() {
const [count, setCount] = useState(0);
const config = useMemo(() => ({ limit: 10 }), []);
return <Child config={config} />;
}
Stale Closures
// Problem: callback captures old state
function Counter() {
const [count, setCount] = useState(0);
const logCount = useCallback(() => {
console.log(count); // Always logs initial value
}, []); // Missing dependency
// Solution: use functional updates or include dependency
const logCountFixed = useCallback(() => {
console.log(count);
}, [count]);
}
Memory Leaks
// Problem: state update after unmount
useEffect(() => {
let isMounted = true;
fetchData().then(data => {
if (isMounted) {
setData(data);
}
});
return () => {
isMounted = false;
};
}, []);
Using Cursor AI for Debugging
Error Analysis
Paste error messages into Cursor:
I'm getting this error in my React app:
"Cannot read properties of undefined (reading 'map')"
Here's the component code:
[paste code]
What's wrong and how do I fix it?
Performance Debugging
This component renders slowly. Here's the code and a React DevTools profiler screenshot:
[paste code]
How can I optimize it?
Common optimizations Cursor suggests:
- Memoize expensive computations with
useMemo - Prevent unnecessary re-renders with
React.memo - Virtualize long lists
- Split large components
State Management Debugging
Redux DevTools
Configure Redux DevTools in development:
// store.ts
import { configureStore } from '@reduxjs/toolkit';
export const store = configureStore({
reducer: {
user: userReducer,
cart: cartReducer,
},
devTools: process.env.NODE_ENV !== 'production',
});
Zustand Store Inspection
// Debug Zustand store
import { useUserStore } from './stores/userStore';
// In browser console
const userStore = useUserStore.getState();
console.log(userStore);
Testing Components Before Debugging
// src/components/__tests__/UserProfile.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import { UserProfile } from '../UserProfile';
describe('UserProfile', () => {
it('handles loading state', () => {
render(<UserProfile userId="1" />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('handles error state', async () => {
render(<UserProfile userId="invalid" />);
await waitFor(() => {
expect(screen.getByText('User not found')).toBeInTheDocument();
});
});
});
Debugging Checklist
| Issue | First Check |
|---|---|
| Component not rendering | Check conditional returns and loading states |
| State not updating | Verify setState calls and dependency arrays |
| Props undefined | Check parent component prop passing |
| API call fails | Inspect network tab and error boundaries |
| Slow performance | Check for unnecessary re-renders |
| Type errors | Verify TypeScript interfaces match data |
Keyboard Shortcuts for Debugging
| Action | Shortcut |
|---|---|
| Toggle breakpoint | F9 |
| Start debugging | F5 |
| Step over | F10 |
| Step into | F11 |
| Step out | Shift + F11 |
| Continue | F5 |
| Open Debug Console | Ctrl + Shift + Y |
Related Resources
Last updated: July 2025