@nhonh/react-debugger 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +362 -36
- package/bin/cli.js +1 -1
- package/docs/GETTING-STARTED.md +267 -0
- package/docs/TABS-GUIDE.md +960 -0
- package/docs/TROUBLESHOOTING.md +416 -0
- package/package.json +7 -5
- package/src/install.js +1 -1
|
@@ -0,0 +1,960 @@
|
|
|
1
|
+
# React Debugger - Complete Tabs Guide
|
|
2
|
+
|
|
3
|
+
Deep dive into each tab's features, metrics, and how to use them effectively.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Timeline Tab](#1-timeline-tab)
|
|
10
|
+
2. [UI & State Tab](#2-ui--state-tab)
|
|
11
|
+
3. [Performance Tab](#3-performance-tab)
|
|
12
|
+
4. [Memory Tab](#4-memory-tab)
|
|
13
|
+
5. [Side Effects Tab](#5-side-effects-tab)
|
|
14
|
+
6. [CLS Tab](#6-cls-tab)
|
|
15
|
+
7. [Redux Tab](#7-redux-tab)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Timeline Tab
|
|
20
|
+
|
|
21
|
+
The Timeline provides a chronological view of everything happening in your React app.
|
|
22
|
+
|
|
23
|
+
### Event Types
|
|
24
|
+
|
|
25
|
+
| Icon | Type | Description |
|
|
26
|
+
|------|------|-------------|
|
|
27
|
+
| 🔄 | **Render** | Component render/re-render |
|
|
28
|
+
| 📦 | **State** | State change (local or Redux) |
|
|
29
|
+
| ⚡ | **Effect** | useEffect execution |
|
|
30
|
+
| ❌ | **Error** | JavaScript error |
|
|
31
|
+
| 🧠 | **Memory** | Memory snapshot |
|
|
32
|
+
| 🔗 | **Context** | Context value change |
|
|
33
|
+
|
|
34
|
+
### Render Events
|
|
35
|
+
|
|
36
|
+
Each render event shows:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
┌─────────────────────────────────────────────────┐
|
|
40
|
+
│ 🔄 MyComponent 12:34:56 │
|
|
41
|
+
│ ─────────────────────────────────────────────── │
|
|
42
|
+
│ Duration: 2.3ms │
|
|
43
|
+
│ Trigger: props (items, onClick) │
|
|
44
|
+
│ Fiber Depth: 5 │
|
|
45
|
+
│ Render Order: #3 in batch │
|
|
46
|
+
└─────────────────────────────────────────────────┘
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Field | Meaning |
|
|
50
|
+
|-------|---------|
|
|
51
|
+
| Duration | How long the render took |
|
|
52
|
+
| Trigger | What caused the render |
|
|
53
|
+
| Fiber Depth | Component depth in tree |
|
|
54
|
+
| Render Order | Position in render batch |
|
|
55
|
+
|
|
56
|
+
### State Change Events
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
┌─────────────────────────────────────────────────┐
|
|
60
|
+
│ 📦 State Change 12:34:57 │
|
|
61
|
+
│ ─────────────────────────────────────────────── │
|
|
62
|
+
│ Component: Counter │
|
|
63
|
+
│ Hook: useState (#0) │
|
|
64
|
+
│ State Name: count │
|
|
65
|
+
│ Old Value: 5 │
|
|
66
|
+
│ New Value: 6 │
|
|
67
|
+
└─────────────────────────────────────────────────┘
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Using Filters
|
|
71
|
+
|
|
72
|
+
Click filter buttons to show/hide event types:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
[🔄 Renders ✓] [📦 State ✓] [⚡ Effects] [❌ Errors ✓]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Search
|
|
79
|
+
|
|
80
|
+
Type in the search box to filter by:
|
|
81
|
+
- Component name
|
|
82
|
+
- Event type
|
|
83
|
+
- Action type (for Redux)
|
|
84
|
+
|
|
85
|
+
### Event Correlation
|
|
86
|
+
|
|
87
|
+
Click any event to highlight related events:
|
|
88
|
+
- Renders that followed a state change
|
|
89
|
+
- Effects triggered by renders
|
|
90
|
+
- Errors with their causes
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 2. UI & State Tab
|
|
95
|
+
|
|
96
|
+
Automatically detects React anti-patterns and common mistakes.
|
|
97
|
+
|
|
98
|
+
### Issue Types
|
|
99
|
+
|
|
100
|
+
#### 🔴 DIRECT_STATE_MUTATION (Error)
|
|
101
|
+
|
|
102
|
+
**Problem:** Modifying state object directly instead of creating new reference.
|
|
103
|
+
|
|
104
|
+
```jsx
|
|
105
|
+
// ❌ Bad - React won't detect the change
|
|
106
|
+
const [user, setUser] = useState({ name: 'John' });
|
|
107
|
+
user.name = 'Jane'; // Direct mutation!
|
|
108
|
+
setUser(user);
|
|
109
|
+
|
|
110
|
+
// ✅ Good - Create new object
|
|
111
|
+
setUser({ ...user, name: 'Jane' });
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Why it matters:** React uses reference equality to detect changes. Mutating the same object doesn't trigger re-renders.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
#### 🟡 MISSING_KEY (Warning)
|
|
119
|
+
|
|
120
|
+
**Problem:** List items rendered without `key` prop.
|
|
121
|
+
|
|
122
|
+
```jsx
|
|
123
|
+
// ❌ Bad
|
|
124
|
+
{items.map(item => <li>{item}</li>)}
|
|
125
|
+
|
|
126
|
+
// ✅ Good
|
|
127
|
+
{items.map(item => <li key={item.id}>{item}</li>)}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Why it matters:** Without keys, React can't track which items changed, leading to bugs and poor performance.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
#### 🟡 INDEX_AS_KEY (Warning)
|
|
135
|
+
|
|
136
|
+
**Problem:** Using array index as key for dynamic lists.
|
|
137
|
+
|
|
138
|
+
```jsx
|
|
139
|
+
// ❌ Bad - problematic when list order changes
|
|
140
|
+
{items.map((item, index) => <li key={index}>{item}</li>)}
|
|
141
|
+
|
|
142
|
+
// ✅ Good - use stable unique ID
|
|
143
|
+
{items.map(item => <li key={item.id}>{item}</li>)}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**When index IS okay:**
|
|
147
|
+
- Static lists that never change
|
|
148
|
+
- No reordering, adding, or removing items
|
|
149
|
+
- Items have no state
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
#### 🔴 DUPLICATE_KEY (Error)
|
|
154
|
+
|
|
155
|
+
**Problem:** Multiple items have the same key.
|
|
156
|
+
|
|
157
|
+
```jsx
|
|
158
|
+
// ❌ Bad - two items with key="1"
|
|
159
|
+
<li key="1">Apple</li>
|
|
160
|
+
<li key="1">Banana</li> // Duplicate!
|
|
161
|
+
|
|
162
|
+
// ✅ Good - unique keys
|
|
163
|
+
<li key="apple">Apple</li>
|
|
164
|
+
<li key="banana">Banana</li>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### Issue Card Anatomy
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
┌─────────────────────────────────────────────────┐
|
|
173
|
+
│ 🔴 DIRECT_STATE_MUTATION │
|
|
174
|
+
│ ─────────────────────────────────────────────── │
|
|
175
|
+
│ Component: UserProfile │
|
|
176
|
+
│ Path: App > Dashboard > UserProfile │
|
|
177
|
+
│ │
|
|
178
|
+
│ Message: State object was mutated directly │
|
|
179
|
+
│ │
|
|
180
|
+
│ 💡 Suggestion: Create a new object/array │
|
|
181
|
+
│ instead of modifying the existing one. │
|
|
182
|
+
│ │
|
|
183
|
+
│ [View Code] [Dismiss] │
|
|
184
|
+
└─────────────────────────────────────────────────┘
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 3. Performance Tab
|
|
190
|
+
|
|
191
|
+
Comprehensive performance analysis with Core Web Vitals and render statistics.
|
|
192
|
+
|
|
193
|
+
### Statistics Dashboard
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
┌──────────────┬──────────────┬──────────────┬──────────────┐
|
|
197
|
+
│ Components │ Total Renders│ Avg Render │ Slow Renders │
|
|
198
|
+
│ 24 │ 156 │ 4.2ms │ 3 │
|
|
199
|
+
└──────────────┴──────────────┴──────────────┴──────────────┘
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
| Metric | Description | Target |
|
|
203
|
+
|--------|-------------|--------|
|
|
204
|
+
| Components | Tracked components | - |
|
|
205
|
+
| Total Renders | Sum of all renders | Lower = better |
|
|
206
|
+
| Avg Render | Average render time | < 16ms |
|
|
207
|
+
| Slow Renders | Renders > 16ms | 0 |
|
|
208
|
+
|
|
209
|
+
### Core Web Vitals
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
┌────────────────────────────────────────────────────────┐
|
|
213
|
+
│ Page Load Metrics │
|
|
214
|
+
│ ────────────────────────────────────────────────────── │
|
|
215
|
+
│ FCP: 1.2s ✅ LCP: 2.1s ✅ TTFB: 0.3s ✅ │
|
|
216
|
+
└────────────────────────────────────────────────────────┘
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
| Metric | Full Name | Good | Needs Work | Poor |
|
|
220
|
+
|--------|-----------|------|------------|------|
|
|
221
|
+
| FCP | First Contentful Paint | < 1.8s | 1.8-3s | > 3s |
|
|
222
|
+
| LCP | Largest Contentful Paint | < 2.5s | 2.5-4s | > 4s |
|
|
223
|
+
| TTFB | Time to First Byte | < 0.8s | 0.8-1.8s | > 1.8s |
|
|
224
|
+
|
|
225
|
+
### Slowest Components Table
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
┌─────────────────┬──────────┬──────────┬─────────┐
|
|
229
|
+
│ Component │ Max Time │ Avg Time │ Renders │
|
|
230
|
+
├─────────────────┼──────────┼──────────┼─────────┤
|
|
231
|
+
│ DataGrid │ 45.2ms │ 23.1ms │ 12 │
|
|
232
|
+
│ Chart │ 32.1ms │ 18.5ms │ 8 │
|
|
233
|
+
│ UserList │ 28.7ms │ 15.2ms │ 24 │
|
|
234
|
+
└─────────────────┴──────────┴──────────┴─────────┘
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Action:** Focus on components with Max Time > 16ms.
|
|
238
|
+
|
|
239
|
+
### Top Re-rendering Components
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
┌─────────────────┬─────────┬──────────┬───────────┬─────────────┐
|
|
243
|
+
│ Component │ Renders │ Avg Time │ Self Time │ Last Trigger│
|
|
244
|
+
├─────────────────┼─────────┼──────────┼───────────┼─────────────┤
|
|
245
|
+
│ SearchInput │ 47 │ 1.2ms │ 0.8ms │ state │
|
|
246
|
+
│ FilterButton │ 32 │ 0.5ms │ 0.3ms │ parent │
|
|
247
|
+
│ ListItem │ 28 │ 0.3ms │ 0.2ms │ props │
|
|
248
|
+
└─────────────────┴─────────┴──────────┴───────────┴─────────────┘
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
| Column | Meaning |
|
|
252
|
+
|--------|---------|
|
|
253
|
+
| Renders | Total render count |
|
|
254
|
+
| Avg Time | Average total render time |
|
|
255
|
+
| Self Time | Time in component (excluding children) |
|
|
256
|
+
| Last Trigger | Most recent render cause |
|
|
257
|
+
|
|
258
|
+
### React Scan (Visual Mode)
|
|
259
|
+
|
|
260
|
+
Toggle ON to see renders directly on the page:
|
|
261
|
+
|
|
262
|
+
| Color | Render Count | Meaning |
|
|
263
|
+
|-------|--------------|---------|
|
|
264
|
+
| 🟢 Green | 1 | Initial mount |
|
|
265
|
+
| 🟡 Yellow | 2-3 | Some re-renders |
|
|
266
|
+
| 🟠 Orange | 4-5 | Frequent re-renders |
|
|
267
|
+
| 🔴 Red | 10+ | Excessive - optimize! |
|
|
268
|
+
|
|
269
|
+
### Optimization Strategies
|
|
270
|
+
|
|
271
|
+
**For `props` trigger:**
|
|
272
|
+
```jsx
|
|
273
|
+
// Wrap with React.memo
|
|
274
|
+
export const MyComponent = React.memo(({ data }) => {
|
|
275
|
+
return <div>{data.name}</div>;
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**For `parent` trigger:**
|
|
280
|
+
```jsx
|
|
281
|
+
// Memoize to prevent re-render when parent updates
|
|
282
|
+
export const Child = React.memo(({ value }) => {
|
|
283
|
+
return <span>{value}</span>;
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**For `state` trigger:**
|
|
288
|
+
```jsx
|
|
289
|
+
// Batch state updates
|
|
290
|
+
const handleClick = () => {
|
|
291
|
+
// React 18+ auto-batches, but be mindful
|
|
292
|
+
setCount(c => c + 1);
|
|
293
|
+
setFlag(f => !f);
|
|
294
|
+
};
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**For `context` trigger:**
|
|
298
|
+
```jsx
|
|
299
|
+
// Split contexts by update frequency
|
|
300
|
+
const ThemeContext = createContext(); // Rarely changes
|
|
301
|
+
const UserContext = createContext(); // Changes on login
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## 4. Memory Tab
|
|
307
|
+
|
|
308
|
+
Monitor JavaScript heap usage and detect memory leaks.
|
|
309
|
+
|
|
310
|
+
### Dashboard
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
┌────────────────────────────────────────────────────────┐
|
|
314
|
+
│ Memory Usage │
|
|
315
|
+
│ ════════════════════════════════════════════════════ │
|
|
316
|
+
│ ████████████████████░░░░░░░░░░░░ 65% │
|
|
317
|
+
│ │
|
|
318
|
+
│ Used: 45.2 MB Total: 69.5 MB Limit: 4.0 GB │
|
|
319
|
+
│ Peak: 52.1 MB Growth: +12 KB/s │
|
|
320
|
+
└────────────────────────────────────────────────────────┘
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Key Metrics
|
|
324
|
+
|
|
325
|
+
| Metric | Description |
|
|
326
|
+
|--------|-------------|
|
|
327
|
+
| Used Heap | Currently allocated memory |
|
|
328
|
+
| Total Heap | Memory available to JS engine |
|
|
329
|
+
| Heap Limit | Maximum allowed |
|
|
330
|
+
| Peak Usage | Highest recorded usage |
|
|
331
|
+
| Growth Rate | Memory change per second |
|
|
332
|
+
|
|
333
|
+
### Health Indicators
|
|
334
|
+
|
|
335
|
+
| Usage | Status | Action |
|
|
336
|
+
|-------|--------|--------|
|
|
337
|
+
| < 70% | ✅ Healthy | No action |
|
|
338
|
+
| 70-90% | ⚠️ Warning | Monitor |
|
|
339
|
+
| > 90% | 🔴 Critical | Investigate |
|
|
340
|
+
|
|
341
|
+
### Growth Rate Analysis
|
|
342
|
+
|
|
343
|
+
| Rate | Status | Meaning |
|
|
344
|
+
|------|--------|---------|
|
|
345
|
+
| Negative | ✅ Good | GC is working |
|
|
346
|
+
| 0 - 512 KB/s | ✅ Normal | Typical fluctuation |
|
|
347
|
+
| 512 KB - 1 MB/s | ⚠️ Warning | Possible leak |
|
|
348
|
+
| > 1 MB/s | 🔴 Critical | Likely memory leak |
|
|
349
|
+
|
|
350
|
+
### Memory Chart
|
|
351
|
+
|
|
352
|
+
The chart shows memory usage over time:
|
|
353
|
+
- **Blue line:** Used heap
|
|
354
|
+
- **Gray line:** Total heap
|
|
355
|
+
- **Spikes:** Indicate allocations
|
|
356
|
+
- **Drops:** Indicate garbage collection
|
|
357
|
+
|
|
358
|
+
### Crash Log
|
|
359
|
+
|
|
360
|
+
Captures errors with memory context:
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
┌─────────────────────────────────────────────────────┐
|
|
364
|
+
│ ❌ TypeError 12:34:56 │
|
|
365
|
+
│ ─────────────────────────────────────────────────── │
|
|
366
|
+
│ Cannot read property 'map' of undefined │
|
|
367
|
+
│ │
|
|
368
|
+
│ Component Stack: │
|
|
369
|
+
│ at UserList (UserList.jsx:15) │
|
|
370
|
+
│ at Dashboard (Dashboard.jsx:42) │
|
|
371
|
+
│ │
|
|
372
|
+
│ Memory at crash: 67.2 MB (78%) │
|
|
373
|
+
└─────────────────────────────────────────────────────┘
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Finding Memory Leaks
|
|
377
|
+
|
|
378
|
+
1. **Start Monitoring** - Click the button
|
|
379
|
+
2. **Create a baseline** - Note initial memory
|
|
380
|
+
3. **Perform actions** - Navigate, open/close modals
|
|
381
|
+
4. **Return to start** - Go back to initial state
|
|
382
|
+
5. **Compare** - Memory should return to baseline
|
|
383
|
+
|
|
384
|
+
**Common leak sources:**
|
|
385
|
+
- Event listeners not removed
|
|
386
|
+
- Timers not cleared
|
|
387
|
+
- Subscriptions not unsubscribed
|
|
388
|
+
- Closures holding references
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## 5. Side Effects Tab
|
|
393
|
+
|
|
394
|
+
Analyze useEffect hooks for common issues.
|
|
395
|
+
|
|
396
|
+
### Issue Categories
|
|
397
|
+
|
|
398
|
+
#### MISSING_CLEANUP
|
|
399
|
+
|
|
400
|
+
```jsx
|
|
401
|
+
// ❌ Bad - timer keeps running after unmount
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
const id = setInterval(() => {
|
|
404
|
+
console.log('tick');
|
|
405
|
+
}, 1000);
|
|
406
|
+
// Missing cleanup!
|
|
407
|
+
}, []);
|
|
408
|
+
|
|
409
|
+
// ✅ Good
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
const id = setInterval(() => {
|
|
412
|
+
console.log('tick');
|
|
413
|
+
}, 1000);
|
|
414
|
+
return () => clearInterval(id); // Cleanup
|
|
415
|
+
}, []);
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
#### MISSING_DEP
|
|
419
|
+
|
|
420
|
+
```jsx
|
|
421
|
+
// ❌ Bad - count not in dependencies
|
|
422
|
+
useEffect(() => {
|
|
423
|
+
document.title = `Count: ${count}`;
|
|
424
|
+
}, []); // Should be [count]
|
|
425
|
+
|
|
426
|
+
// ✅ Good
|
|
427
|
+
useEffect(() => {
|
|
428
|
+
document.title = `Count: ${count}`;
|
|
429
|
+
}, [count]);
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
#### INFINITE_LOOP_RISK
|
|
433
|
+
|
|
434
|
+
```jsx
|
|
435
|
+
// ❌ Bad - updates state that triggers effect again
|
|
436
|
+
useEffect(() => {
|
|
437
|
+
setCount(count + 1); // Infinite loop!
|
|
438
|
+
}, [count]);
|
|
439
|
+
|
|
440
|
+
// ✅ Good - use functional update
|
|
441
|
+
useEffect(() => {
|
|
442
|
+
setCount(c => c + 1);
|
|
443
|
+
}, []); // Run once
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
#### STALE_CLOSURE
|
|
447
|
+
|
|
448
|
+
```jsx
|
|
449
|
+
// ❌ Bad - callback captures stale count value
|
|
450
|
+
useEffect(() => {
|
|
451
|
+
const handler = () => {
|
|
452
|
+
console.log(count); // Always logs initial value
|
|
453
|
+
};
|
|
454
|
+
window.addEventListener('click', handler);
|
|
455
|
+
return () => window.removeEventListener('click', handler);
|
|
456
|
+
}, []); // Missing count
|
|
457
|
+
|
|
458
|
+
// ✅ Good - use ref for latest value
|
|
459
|
+
const countRef = useRef(count);
|
|
460
|
+
countRef.current = count;
|
|
461
|
+
|
|
462
|
+
useEffect(() => {
|
|
463
|
+
const handler = () => {
|
|
464
|
+
console.log(countRef.current); // Always current
|
|
465
|
+
};
|
|
466
|
+
window.addEventListener('click', handler);
|
|
467
|
+
return () => window.removeEventListener('click', handler);
|
|
468
|
+
}, []);
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Effect Card Details
|
|
472
|
+
|
|
473
|
+
```
|
|
474
|
+
┌─────────────────────────────────────────────────────┐
|
|
475
|
+
│ ⚠️ MISSING_CLEANUP │
|
|
476
|
+
│ ─────────────────────────────────────────────────── │
|
|
477
|
+
│ Component: DataFetcher │
|
|
478
|
+
│ Effect Index: #0 │
|
|
479
|
+
│ │
|
|
480
|
+
│ Dependencies: [userId] │
|
|
481
|
+
│ Has Cleanup: No ❌ │
|
|
482
|
+
│ │
|
|
483
|
+
│ Effect Preview: │
|
|
484
|
+
│ fetch(`/api/user/${userId}`) │
|
|
485
|
+
│ .then(res => setUser(res)) │
|
|
486
|
+
│ │
|
|
487
|
+
│ 💡 Tip: Use AbortController for fetch cleanup │
|
|
488
|
+
└─────────────────────────────────────────────────────┘
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## 6. CLS Tab
|
|
494
|
+
|
|
495
|
+
Monitor Cumulative Layout Shift - a Core Web Vital for visual stability.
|
|
496
|
+
|
|
497
|
+
### Score Interpretation
|
|
498
|
+
|
|
499
|
+
| Score | Rating | User Experience |
|
|
500
|
+
|-------|--------|-----------------|
|
|
501
|
+
| < 0.1 | ✅ Good | Stable, smooth |
|
|
502
|
+
| 0.1 - 0.25 | ⚠️ Needs Improvement | Noticeable shifts |
|
|
503
|
+
| > 0.25 | 🔴 Poor | Frustrating experience |
|
|
504
|
+
|
|
505
|
+
### Dashboard
|
|
506
|
+
|
|
507
|
+
```
|
|
508
|
+
┌────────────────────────────────────────────────────────┐
|
|
509
|
+
│ CLS Score: 0.15 │
|
|
510
|
+
│ ████████████████░░░░░░░░░░░░░░ ⚠️ Needs Improvement │
|
|
511
|
+
│ │
|
|
512
|
+
│ Total Shifts: 5 Last Shift: 2.3s ago │
|
|
513
|
+
└────────────────────────────────────────────────────────┘
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Top Shift Sources
|
|
517
|
+
|
|
518
|
+
```
|
|
519
|
+
┌─────────────────────────────────┬────────────┬────────────┐
|
|
520
|
+
│ Element │ Total Shift│ Occurrences│
|
|
521
|
+
├─────────────────────────────────┼────────────┼────────────┤
|
|
522
|
+
│ img.hero-image │ 0.08 │ 1 │
|
|
523
|
+
│ div.ad-container │ 0.05 │ 3 │
|
|
524
|
+
│ p.dynamic-content │ 0.02 │ 2 │
|
|
525
|
+
└─────────────────────────────────┴────────────┴────────────┘
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Common Causes & Fixes
|
|
529
|
+
|
|
530
|
+
| Cause | Fix |
|
|
531
|
+
|-------|-----|
|
|
532
|
+
| Images without dimensions | Add `width` and `height` attributes |
|
|
533
|
+
| Ads/embeds | Set explicit container dimensions |
|
|
534
|
+
| Dynamic content | Reserve space with `min-height` |
|
|
535
|
+
| Web fonts | Use `font-display: swap` |
|
|
536
|
+
| Animations | Use `transform` instead of `top/left` |
|
|
537
|
+
|
|
538
|
+
**Image fix:**
|
|
539
|
+
```jsx
|
|
540
|
+
// ❌ Bad
|
|
541
|
+
<img src="photo.jpg" alt="Photo" />
|
|
542
|
+
|
|
543
|
+
// ✅ Good
|
|
544
|
+
<img src="photo.jpg" alt="Photo" width={800} height={600} />
|
|
545
|
+
|
|
546
|
+
// ✅ Also good - aspect ratio
|
|
547
|
+
<img
|
|
548
|
+
src="photo.jpg"
|
|
549
|
+
alt="Photo"
|
|
550
|
+
style={{ aspectRatio: '16/9', width: '100%' }}
|
|
551
|
+
/>
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
**Dynamic content fix:**
|
|
555
|
+
```jsx
|
|
556
|
+
// ❌ Bad - content pushes things down
|
|
557
|
+
{loaded && <Content />}
|
|
558
|
+
|
|
559
|
+
// ✅ Good - space reserved
|
|
560
|
+
<div style={{ minHeight: 200 }}>
|
|
561
|
+
{loaded ? <Content /> : <Skeleton />}
|
|
562
|
+
</div>
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
## 7. Redux Tab
|
|
568
|
+
|
|
569
|
+
**The most powerful Redux debugging experience** - view state, edit values live, dispatch actions, and manipulate arrays directly.
|
|
570
|
+
|
|
571
|
+
### Overview
|
|
572
|
+
|
|
573
|
+
```
|
|
574
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
575
|
+
│ 🗄️ Redux DevTools [🔄] │
|
|
576
|
+
├────────────────────────┬────────────────────────────────────────┤
|
|
577
|
+
│ Action History (12) │ State Tree [🔍] [+] [−] [⟲]│
|
|
578
|
+
│ ───────────────────────│────────────────────────────────────────│
|
|
579
|
+
│ 12:34:56 user/login │ ▼ user │
|
|
580
|
+
│ 12:34:58 posts/fetch │ ├─ id: 123 ← click to edit │
|
|
581
|
+
│ 12:35:01 ui/toggle │ ├─ name: "John" ← click to edit │
|
|
582
|
+
│ 12:35:03 cart/add ◀── │ └─ role: "admin" │
|
|
583
|
+
│ │ ▼ cart │
|
|
584
|
+
│ [Action Details] │ └─ items: Array(3) [↑] [↓] [×] │
|
|
585
|
+
└────────────────────────┴────────────────────────────────────────┘
|
|
586
|
+
│ Dispatch Action │
|
|
587
|
+
│ Type: [cart/addItem ] Payload: [{ "id": 4 }] [Send] │
|
|
588
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
### 🌳 State Tree Browser
|
|
594
|
+
|
|
595
|
+
Interactive tree view of your entire Redux state.
|
|
596
|
+
|
|
597
|
+
```
|
|
598
|
+
┌─────────────────────────────────────────────────────┐
|
|
599
|
+
│ 🔍 Search state... [+] [−] [⟲] │
|
|
600
|
+
├─────────────────────────────────────────────────────┤
|
|
601
|
+
│ ▼ user │
|
|
602
|
+
│ ├─ id: 123 ← Number (editable)│
|
|
603
|
+
│ ├─ name: "John Doe" ← String (editable)│
|
|
604
|
+
│ ├─ isAdmin: true ← Boolean (toggle) │
|
|
605
|
+
│ └─ email: "john@example.com" │
|
|
606
|
+
│ ▼ cart │
|
|
607
|
+
│ ├─ total: 99.99 │
|
|
608
|
+
│ └▼ items: Array(2) │
|
|
609
|
+
│ ├─ [0]: { id: 1, name: "Book" } [↑][↓][×] │
|
|
610
|
+
│ └─ [1]: { id: 2, name: "Pen" } [↑][↓][×] │
|
|
611
|
+
│ ▶ settings (collapsed - click to expand) │
|
|
612
|
+
└─────────────────────────────────────────────────────┘
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
#### Controls
|
|
616
|
+
|
|
617
|
+
| Button | Action |
|
|
618
|
+
|--------|--------|
|
|
619
|
+
| `+` | Expand all nodes |
|
|
620
|
+
| `−` | Collapse all nodes |
|
|
621
|
+
| `⟲` | Reset all edited values |
|
|
622
|
+
| `🔄` | Refresh state from store |
|
|
623
|
+
|
|
624
|
+
#### Search
|
|
625
|
+
|
|
626
|
+
Type in the search box to filter state keys:
|
|
627
|
+
- Searches both key names and values
|
|
628
|
+
- Great for finding specific data in large state trees
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
### ✏️ Live State Editing (Key Feature!)
|
|
633
|
+
|
|
634
|
+
**Click any value to edit it directly** - changes apply immediately via Redux.
|
|
635
|
+
|
|
636
|
+
#### Supported Types
|
|
637
|
+
|
|
638
|
+
| Type | How to Edit |
|
|
639
|
+
|------|-------------|
|
|
640
|
+
| **String** | Click → Type new value → Enter |
|
|
641
|
+
| **Number** | Click → Type number → Enter |
|
|
642
|
+
| **Boolean** | Click → Dropdown (true/false) |
|
|
643
|
+
| **Object/Array** | Click → JSON editor → Enter |
|
|
644
|
+
| **null** | Click → Edit as any type |
|
|
645
|
+
|
|
646
|
+
#### Editing Workflow
|
|
647
|
+
|
|
648
|
+
```
|
|
649
|
+
1. Click on a value:
|
|
650
|
+
name: "John" → name: [John ] [✓] [✗]
|
|
651
|
+
↑ editable input
|
|
652
|
+
|
|
653
|
+
2. Modify the value:
|
|
654
|
+
name: [Jane ] [✓] [✗]
|
|
655
|
+
|
|
656
|
+
3. Press Enter or click ✓ to save
|
|
657
|
+
Changes apply immediately to Redux store!
|
|
658
|
+
|
|
659
|
+
4. Press Escape or click ✗ to cancel
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
#### Keyboard Shortcuts
|
|
663
|
+
|
|
664
|
+
| Key | Action |
|
|
665
|
+
|-----|--------|
|
|
666
|
+
| `Enter` | Save changes |
|
|
667
|
+
| `Escape` | Cancel editing |
|
|
668
|
+
|
|
669
|
+
#### Example Use Cases
|
|
670
|
+
|
|
671
|
+
```jsx
|
|
672
|
+
// Testing different user roles
|
|
673
|
+
user.role: "admin" → "moderator" → "user"
|
|
674
|
+
|
|
675
|
+
// Adjusting cart totals
|
|
676
|
+
cart.total: 99.99 → 0 → 150.00
|
|
677
|
+
|
|
678
|
+
// Toggling feature flags
|
|
679
|
+
features.darkMode: false → true
|
|
680
|
+
|
|
681
|
+
// Modifying complex objects
|
|
682
|
+
user.preferences: { theme: "light" } → { theme: "dark", fontSize: 16 }
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
687
|
+
### 📦 Array Manipulation
|
|
688
|
+
|
|
689
|
+
**Powerful array controls** for each item in arrays:
|
|
690
|
+
|
|
691
|
+
```
|
|
692
|
+
▼ cart.items: Array(3)
|
|
693
|
+
├─ [0]: { id: 1, name: "Book" } [↑] [↓] [×]
|
|
694
|
+
├─ [1]: { id: 2, name: "Pen" } [↑] [↓] [×]
|
|
695
|
+
└─ [2]: { id: 3, name: "Paper" } [↑] [↓] [×]
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
| Button | Action | Use Case |
|
|
699
|
+
|--------|--------|----------|
|
|
700
|
+
| `↑` | Move item up | Reorder list items |
|
|
701
|
+
| `↓` | Move item down | Reorder list items |
|
|
702
|
+
| `×` | Delete item | Remove from array |
|
|
703
|
+
|
|
704
|
+
#### Example: Reordering Cart Items
|
|
705
|
+
|
|
706
|
+
```
|
|
707
|
+
Before: After clicking ↑ on [1]:
|
|
708
|
+
├─ [0]: Book ├─ [0]: Pen ← moved up
|
|
709
|
+
├─ [1]: Pen ├─ [1]: Book ← moved down
|
|
710
|
+
└─ [2]: Paper └─ [2]: Paper
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
---
|
|
714
|
+
|
|
715
|
+
### 📜 Action History
|
|
716
|
+
|
|
717
|
+
See every Redux action dispatched in your app:
|
|
718
|
+
|
|
719
|
+
```
|
|
720
|
+
┌─────────────────────────────────────────────────────┐
|
|
721
|
+
│ Action History (47) │
|
|
722
|
+
├─────────────────────────────────────────────────────┤
|
|
723
|
+
│ 12:34:56 user/login │
|
|
724
|
+
│ 12:34:58 posts/fetchPending │
|
|
725
|
+
│ 12:35:01 posts/fetchSuccess │
|
|
726
|
+
│ 12:35:03 cart/addItem ← Click to select │
|
|
727
|
+
│ 12:35:05 ui/openModal │
|
|
728
|
+
└─────────────────────────────────────────────────────┘
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
#### Click an Action to See Details
|
|
732
|
+
|
|
733
|
+
```
|
|
734
|
+
┌─────────────────────────────────────────────────────┐
|
|
735
|
+
│ Action: cart/addItem │
|
|
736
|
+
├─────────────────────────────────────────────────────┤
|
|
737
|
+
│ Payload: │
|
|
738
|
+
│ { │
|
|
739
|
+
│ "productId": 123, │
|
|
740
|
+
│ "quantity": 2, │
|
|
741
|
+
│ "price": 29.99 │
|
|
742
|
+
│ } │
|
|
743
|
+
└─────────────────────────────────────────────────────┘
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
**Use cases:**
|
|
747
|
+
- Debug why state changed unexpectedly
|
|
748
|
+
- Verify actions are dispatched correctly
|
|
749
|
+
- Check action payloads for errors
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
753
|
+
### 🚀 Action Dispatcher
|
|
754
|
+
|
|
755
|
+
**Test your reducers** by dispatching custom actions:
|
|
756
|
+
|
|
757
|
+
```
|
|
758
|
+
┌─────────────────────────────────────────────────────┐
|
|
759
|
+
│ Dispatch Action │
|
|
760
|
+
├─────────────────────────────────────────────────────┤
|
|
761
|
+
│ Type: │
|
|
762
|
+
│ [cart/addItem ] │
|
|
763
|
+
│ │
|
|
764
|
+
│ Payload (JSON): │
|
|
765
|
+
│ [ ] │
|
|
766
|
+
│ [{ ] │
|
|
767
|
+
│ [ "productId": 999, ] │
|
|
768
|
+
│ [ "quantity": 1 ] │
|
|
769
|
+
│ [} ] │
|
|
770
|
+
│ │
|
|
771
|
+
│ [Dispatch Action] │
|
|
772
|
+
└─────────────────────────────────────────────────────┘
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
#### Common Testing Scenarios
|
|
776
|
+
|
|
777
|
+
**Test edge cases:**
|
|
778
|
+
```json
|
|
779
|
+
// Empty cart
|
|
780
|
+
Type: cart/clear
|
|
781
|
+
Payload: {}
|
|
782
|
+
|
|
783
|
+
// Add item with invalid data
|
|
784
|
+
Type: cart/addItem
|
|
785
|
+
Payload: { "productId": null, "quantity": -1 }
|
|
786
|
+
|
|
787
|
+
// Simulate API error
|
|
788
|
+
Type: api/error
|
|
789
|
+
Payload: { "code": 500, "message": "Server error" }
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
**Test user flows:**
|
|
793
|
+
```json
|
|
794
|
+
// Login as different user
|
|
795
|
+
Type: auth/loginSuccess
|
|
796
|
+
Payload: { "userId": 456, "role": "admin" }
|
|
797
|
+
|
|
798
|
+
// Toggle feature flag
|
|
799
|
+
Type: features/toggle
|
|
800
|
+
Payload: { "feature": "darkMode", "enabled": true }
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
---
|
|
804
|
+
|
|
805
|
+
### 🔍 Redux Detection Methods
|
|
806
|
+
|
|
807
|
+
The extension automatically finds your Redux store via:
|
|
808
|
+
|
|
809
|
+
| Method | Priority | Description |
|
|
810
|
+
|--------|----------|-------------|
|
|
811
|
+
| `window.store` | 1st | Explicitly exposed store |
|
|
812
|
+
| `window.__REDUX_STORE__` | 2nd | Alternative naming |
|
|
813
|
+
| Redux DevTools Extension | 3rd | Uses existing connection |
|
|
814
|
+
| React-Redux Provider | 4th | Finds store in React fiber tree |
|
|
815
|
+
|
|
816
|
+
#### Recommended Setup
|
|
817
|
+
|
|
818
|
+
```jsx
|
|
819
|
+
// store.js
|
|
820
|
+
import { configureStore } from '@reduxjs/toolkit';
|
|
821
|
+
import rootReducer from './reducers';
|
|
822
|
+
|
|
823
|
+
const store = configureStore({
|
|
824
|
+
reducer: rootReducer,
|
|
825
|
+
devTools: process.env.NODE_ENV !== 'production',
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
// Expose for React Debugger (development only)
|
|
829
|
+
if (process.env.NODE_ENV === 'development') {
|
|
830
|
+
window.store = store;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
export default store;
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
#### Redux Toolkit (Recommended)
|
|
837
|
+
|
|
838
|
+
```jsx
|
|
839
|
+
// RTK automatically connects to DevTools
|
|
840
|
+
import { configureStore } from '@reduxjs/toolkit';
|
|
841
|
+
|
|
842
|
+
const store = configureStore({
|
|
843
|
+
reducer: {
|
|
844
|
+
user: userReducer,
|
|
845
|
+
cart: cartReducer,
|
|
846
|
+
},
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
// That's it! DevTools connection is automatic
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
#### Legacy Redux
|
|
853
|
+
|
|
854
|
+
```jsx
|
|
855
|
+
import { createStore, applyMiddleware, compose } from 'redux';
|
|
856
|
+
|
|
857
|
+
const composeEnhancers =
|
|
858
|
+
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
|
859
|
+
|
|
860
|
+
const store = createStore(
|
|
861
|
+
rootReducer,
|
|
862
|
+
composeEnhancers(applyMiddleware(...middleware))
|
|
863
|
+
);
|
|
864
|
+
|
|
865
|
+
window.store = store; // For React Debugger
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
---
|
|
869
|
+
|
|
870
|
+
### 💡 Pro Tips
|
|
871
|
+
|
|
872
|
+
#### 1. Quick State Reset
|
|
873
|
+
|
|
874
|
+
Made too many edits? Click `⟲` to reset all values to original.
|
|
875
|
+
|
|
876
|
+
#### 2. Test Loading States
|
|
877
|
+
|
|
878
|
+
```json
|
|
879
|
+
Type: posts/fetchPending
|
|
880
|
+
Payload: {}
|
|
881
|
+
// Then check your loading UI
|
|
882
|
+
|
|
883
|
+
Type: posts/fetchSuccess
|
|
884
|
+
Payload: { "posts": [...] }
|
|
885
|
+
// Check success state
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
#### 3. Simulate Errors
|
|
889
|
+
|
|
890
|
+
```json
|
|
891
|
+
Type: posts/fetchError
|
|
892
|
+
Payload: { "error": "Network timeout" }
|
|
893
|
+
// Check error handling UI
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
#### 4. Debug Selectors
|
|
897
|
+
|
|
898
|
+
Edit state values to see if selectors update correctly:
|
|
899
|
+
```
|
|
900
|
+
user.subscription: "free" → "premium"
|
|
901
|
+
// Watch if premium features appear
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
#### 5. Test Permissions
|
|
905
|
+
|
|
906
|
+
```
|
|
907
|
+
user.role: "user" → "admin"
|
|
908
|
+
// Verify admin-only features show/hide
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
---
|
|
912
|
+
|
|
913
|
+
### ⚠️ Troubleshooting
|
|
914
|
+
|
|
915
|
+
#### "Redux not detected"
|
|
916
|
+
|
|
917
|
+
Check the Setup Guide shown in the tab:
|
|
918
|
+
|
|
919
|
+
1. Verify Redux is actually used in the app
|
|
920
|
+
2. Expose store via `window.store`
|
|
921
|
+
3. Install Redux DevTools browser extension
|
|
922
|
+
4. Refresh the page
|
|
923
|
+
|
|
924
|
+
#### State doesn't update after edit
|
|
925
|
+
|
|
926
|
+
1. Click `🔄` Refresh button
|
|
927
|
+
2. Check browser console for errors
|
|
928
|
+
3. Verify reducer handles the action
|
|
929
|
+
|
|
930
|
+
#### Actions not appearing
|
|
931
|
+
|
|
932
|
+
1. Make sure Recording is enabled (green badge)
|
|
933
|
+
2. Actions must be dispatched after opening DevTools
|
|
934
|
+
3. Check if middleware is blocking actions
|
|
935
|
+
|
|
936
|
+
---
|
|
937
|
+
|
|
938
|
+
## Quick Reference
|
|
939
|
+
|
|
940
|
+
### When to Use Each Tab
|
|
941
|
+
|
|
942
|
+
| Scenario | Tab |
|
|
943
|
+
|----------|-----|
|
|
944
|
+
| "What just happened?" | Timeline |
|
|
945
|
+
| "Is my code correct?" | UI & State |
|
|
946
|
+
| "Why is it slow?" | Performance |
|
|
947
|
+
| "Is there a leak?" | Memory |
|
|
948
|
+
| "Are my effects right?" | Side Effects |
|
|
949
|
+
| "Why does it jump?" | CLS |
|
|
950
|
+
| "What's in my store?" | Redux |
|
|
951
|
+
|
|
952
|
+
### Metric Thresholds
|
|
953
|
+
|
|
954
|
+
| Metric | Good | Warning | Poor |
|
|
955
|
+
|--------|------|---------|------|
|
|
956
|
+
| Render time | < 16ms | 16-50ms | > 50ms |
|
|
957
|
+
| Memory usage | < 70% | 70-90% | > 90% |
|
|
958
|
+
| CLS score | < 0.1 | 0.1-0.25 | > 0.25 |
|
|
959
|
+
| FCP | < 1.8s | 1.8-3s | > 3s |
|
|
960
|
+
| LCP | < 2.5s | 2.5-4s | > 4s |
|