React mit Cursor debuggen: Hooks, State und Performance

React-Anwendungen können aufgrund von State-Management, Hook-Abhängigkeiten und Rendering-Optimierung komplexe Bugs aufweisen. Die KI-Funktionen von Cursor können Ihnen helfen, den State-Flow zu verfolgen, unnötige Re-Renders zu identifizieren und Hook-Probleme zu debuggen. Dieser Leitfaden deckt gängige React-Debugging-Szenarien ab.
Voraussetzungen
Bevor Sie beginnen:
- Cursor installiert (neueste Version empfohlen)
- React DevTools Browser-Erweiterung
- Vertrautheit mit React Hooks (useState, useEffect, useMemo)
- Verständnis des React-Renderzyklus
Debug-Umgebung einrichten
Empfohlene Cursor-Einstellungen
// .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.preferences.importModuleSpecifier": "relative",
"editor.quickSuggestions": {
"strings": true
}
}
React DevTools-Integration
- Installieren Sie React DevTools für Ihren Browser
- Öffnen Sie Ihr Projekt in Cursor
- Verwenden Sie den Components-Tab zur Inspektion der Komponentenbaum
- Verwenden Sie den Profiler-Tab zur Messung der Render-Performance
React Hooks debuggen
useEffect-Abhängigkeitsprobleme
Der häufigste Fehler mit Hooks betrifft fehlende Abhängigkeiten:
// ❌ Fehler: Fehlende Abhängigkeit
useEffect(() => {
fetchUser(userId);
}, []); // eslint-disable-line react-hooks/exhaustive-deps
Lassen Sie Cursor korrigieren:
Dieser useEffect hat eine fehlende Abhängigkeitswarnung. Behebe sie und vermeide dabei Endlosschleifen.
Cursor schlägt vor:
// ✅ Richtig: Alle Abhängigkeiten einbeziehen
useEffect(() => {
fetchUser(userId);
}, [userId]); // userId ist jetzt enthalten
Benutzerdefinierte Hooks debuggen
// src/hooks/useLocalStorage.ts
import { useState, useEffect, useCallback } from 'react';
export function useLocalStorage<T>(key: string, initialValue: T) {
// Faule Initialisierung, um localStorage bei jedem Render zu vermeiden
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;
}
});
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;
}
State-Flow debuggen
Prop Drilling verfolgen
Wenn Daten durch mehrere Ebenen gehen, verwenden Sie Cursor zum Refactoring auf Context:
// ❌ Vorher: Prop Drilling
function App() {
const [user, setUser] = useState(null);
return <Layout user={user} setUser={setUser} />;
}
function Layout({ user, setUser }) {
return <Header user={user} setUser={setUser} />;
}
// ✅ Nachher: Context verwenden
const UserContext = createContext(null);
function App() {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
<Layout />
</UserContext.Provider>
);
}
Context-Consumer debuggen
// Verwendung von useContextSelector für granuläre Abonnements
import { createContext, useContext, useCallback } from 'react';
const ThemeContext = createContext(null);
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// In der Komponente
function ThemedButton() {
const { theme, toggleTheme } = useTheme();
// Re-render nur wenn theme sich ändert, nicht alles im Context
return (
<button
style={{ background: theme.primary }}
onClick={toggleTheme}
>
Toggle Theme
</button>
);
}
Performance debuggen
Unnötige Renders identifizieren
Verwenden Sie den React DevTools Profiler:
- Öffnen Sie den Profiler-Tab
- Klicken Sie auf Record (Aufzeichnen)
- Interagieren Sie mit der Anwendung
- Stoppen Sie die Aufzeichnung und analysieren Sie das Flammendiagramm
Optimierung mit memo
import { memo, useMemo, useCallback } from 'react';
// Komponente wrappen, um unnötige Renders zu verhindern
const ExpensiveList = memo(function ExpensiveList({ items, onSelect }) {
return (
<ul>
{items.map(item => (
<ListItem key={item.id} item={item} onSelect={onSelect} />
))}
</ul>
);
});
// In der Elternkomponente
function Parent() {
const [items, setItems] = useState([]);
const [filter, setFilter] = useState('');
// Gefilterte Liste memorieren
const filteredItems = useMemo(
() => items.filter(item => item.name.includes(filter)),
[items, filter]
);
// Callback memorieren, um Referenzgleichheit zu wahren
const handleSelect = useCallback((id: string) => {
console.log('Selected:', id);
}, []);
return (
<ExpensiveList items={filteredItems} onSelect={handleSelect} />
);
}
useMemo und useCallback Guide
| Hook | Verwendet für | Beispiel |
|---|---|---|
useMemo | Teure Berechnungen | Filtern/Sortieren großer Arrays |
useCallback | Callbacks an Kinder übergeben | Event-Handler |
memo | Reine Komponentenoptimierung | Große Listen rendern |
Profilen Sie die Performance, bevor Sie useMemo/useCallback hinzufügen. Cursor kann Ihnen helfen, echte Performance-Engpässe zu identifizieren.
Häufige React-Fehler
Endlosschleifen
Too many re-renders. React limits the number of renders to prevent an infinite loop.
Ursache : State direkt während des Renderns setzen
// ❌ Fehler: State während des Renderns setzen
function BadComponent() {
const [count, setCount] = useState(0);
setCount(count + 1); // Verursacht Endlosschleife
return <div>{count}</div>;
}
// ✅ Richtig: State im Event-Handler setzen
function GoodComponent() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}
Veralteter Closure
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
// ❌ Fehler: 'count' ist immer 0
console.log(count);
setCount(count + 1);
}, 1000);
return () => clearInterval(interval);
}, []); // Fehlende count-Abhängigkeit
// ✅ Richtig: Funktionales Update verwenden
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1); // Verwendet immer den neuesten Wert
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>{count}</div>;
}
Hydration-Mismatch
Warning: Text content did not match. Server: "..." Client: "..."
Verwenden Sie Cursor, um SSR-Probleme zu beheben:
// Dynamischer Inhalt, der nur clientseitig gerendert wird
function ClientDate() {
const [date, setDate] = useState<string | null>(null);
useEffect(() => {
setDate(new Date().toLocaleDateString());
}, []);
if (!date) return <span>Loading...</span>;
return <span>{date}</span>;
}
Asynchrone Operationen debuggen
Daten-Fetching-Pattern
// src/hooks/useApi.ts
import { useState, useEffect, useRef } from 'react';
export function useApi<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const abortController = useRef<AbortController | null>(null);
useEffect(() => {
abortController.current = new AbortController();
async function fetchData() {
try {
setLoading(true);
setError(null);
const response = await fetch(url, {
signal: abortController.current?.signal,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err);
}
} finally {
setLoading(false);
}
}
fetchData();
return () => {
abortController.current?.abort();
};
}, [url]);
return { data, loading, error };
}
Error Boundaries mit Cursor
// src/components/ErrorBoundary.tsx
import { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
export class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false, error: null };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('ErrorBoundary caught:', error, errorInfo);
// Fehler an Überwachungsdienst senden
}
render() {
if (this.state.hasError) {
return (
this.props.fallback || (
<div role="alert">
<h2>Something went wrong</h2>
<details>
<summary>Error details</summary>
<pre>{this.state.error?.message}</pre>
</details>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
)
);
}
return this.props.children;
}
}
Schnelle Debug-Checkliste
| Problem | Überprüfen |
|---|---|
| Komponente rendert zu oft | React DevTools Profiler |
| State aktualisiert sich nicht | useEffect-Abhängigkeiten |
| Context-Wert veraltet | Provider-Wert-Referenz |
| Memory Leak | useEffect-Cleanup-Funktion |
| Hydration-Fehler | Client/Server-Inhaltsunterschied |
| Endlosschleife | State-Update während des Renderns |
Verwandte Ressourcen
Zuletzt aktualisiert: Juli 2025