0
# Development Tools
1
2
Development utilities, debugging helpers, and React DevTools integration for enhanced developer experience. These tools help with debugging, profiling, and understanding component behavior during development.
3
4
## Capabilities
5
6
### Debug Module Utilities
7
8
Development utilities for debugging and inspecting component behavior.
9
10
```typescript { .api }
11
/**
12
* Resets the history of prop warnings
13
* Useful for clearing accumulated warnings in development
14
*/
15
function resetPropWarnings(): void;
16
17
/**
18
* Gets the currently rendering VNode
19
* @returns The VNode currently being processed or null
20
*/
21
function getCurrentVNode(): VNode | null;
22
23
/**
24
* Gets the display name of a component from a VNode
25
* @param vnode - VNode to extract display name from
26
* @returns Component display name or element type
27
*/
28
function getDisplayName(vnode: VNode): string;
29
30
/**
31
* Gets the component stack trace for debugging
32
* @param vnode - VNode to get stack trace for
33
* @returns String representation of component hierarchy
34
*/
35
function getOwnerStack(vnode: VNode): string;
36
```
37
38
**Usage Examples:**
39
40
```typescript
41
import {
42
resetPropWarnings,
43
getCurrentVNode,
44
getDisplayName,
45
getOwnerStack,
46
createElement
47
} from "preact/debug";
48
49
// Development debugging helper
50
function DebugInfo() {
51
const handleDebugClick = () => {
52
const currentVNode = getCurrentVNode();
53
54
if (currentVNode) {
55
console.log("Current VNode:", currentVNode);
56
console.log("Display Name:", getDisplayName(currentVNode));
57
console.log("Owner Stack:", getOwnerStack(currentVNode));
58
} else {
59
console.log("No VNode currently rendering");
60
}
61
};
62
63
const clearWarnings = () => {
64
resetPropWarnings();
65
console.log("Prop warnings cleared");
66
};
67
68
return createElement("div", { className: "debug-panel" },
69
createElement("h3", null, "Debug Tools"),
70
createElement("button", { onClick: handleDebugClick }, "Log Current VNode"),
71
createElement("button", { onClick: clearWarnings }, "Clear Prop Warnings")
72
);
73
}
74
75
// Custom hook for component debugging
76
function useComponentDebug(componentName: string) {
77
useEffect(() => {
78
console.log(`${componentName} mounted`);
79
80
return () => {
81
console.log(`${componentName} unmounted`);
82
};
83
}, [componentName]);
84
85
useEffect(() => {
86
const currentVNode = getCurrentVNode();
87
if (currentVNode) {
88
console.log(`${componentName} rendered:`, {
89
displayName: getDisplayName(currentVNode),
90
ownerStack: getOwnerStack(currentVNode)
91
});
92
}
93
});
94
}
95
96
// Component with debug information
97
function DebuggableComponent({ data }: { data: any }) {
98
useComponentDebug('DebuggableComponent');
99
100
return createElement("div", null,
101
createElement("h4", null, "Debuggable Component"),
102
createElement("pre", null, JSON.stringify(data, null, 2))
103
);
104
}
105
```
106
107
### DevTools Integration
108
109
React DevTools integration for component inspection and profiling.
110
111
```typescript { .api }
112
/**
113
* Adds a custom name to a hook value for React DevTools display
114
* @param value - The hook value to name
115
* @param name - Custom name to display in DevTools
116
* @returns The original value unchanged
117
*/
118
function addHookName<T>(value: T, name: string): T;
119
```
120
121
**Usage Examples:**
122
123
```typescript
124
import { addHookName } from "preact/devtools";
125
import { useState, useEffect, useMemo } from "preact/hooks";
126
127
// Custom hook with DevTools naming
128
function useCounter(initialValue = 0, step = 1) {
129
const [count, setCount] = useState(initialValue);
130
131
const increment = () => setCount(c => c + step);
132
const decrement = () => setCount(c => c - step);
133
const reset = () => setCount(initialValue);
134
135
// These will show up with custom names in React DevTools
136
return {
137
count: addHookName(count, 'Count'),
138
increment: addHookName(increment, 'Increment'),
139
decrement: addHookName(decrement, 'Decrement'),
140
reset: addHookName(reset, 'Reset')
141
};
142
}
143
144
// Complex custom hook with multiple named values
145
function useApiData<T>(url: string) {
146
const [data, setData] = useState<T | null>(null);
147
const [loading, setLoading] = useState(true);
148
const [error, setError] = useState<Error | null>(null);
149
150
useEffect(() => {
151
let cancelled = false;
152
153
const fetchData = async () => {
154
try {
155
setLoading(true);
156
setError(null);
157
158
const response = await fetch(url);
159
if (!response.ok) {
160
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
161
}
162
163
const result = await response.json();
164
165
if (!cancelled) {
166
setData(result);
167
setLoading(false);
168
}
169
} catch (err) {
170
if (!cancelled) {
171
setError(err instanceof Error ? err : new Error(String(err)));
172
setLoading(false);
173
}
174
}
175
};
176
177
fetchData();
178
179
return () => {
180
cancelled = true;
181
};
182
}, [url]);
183
184
const refetch = () => {
185
setData(null);
186
setLoading(true);
187
setError(null);
188
};
189
190
// Named values for better DevTools experience
191
return {
192
data: addHookName(data, 'API Data'),
193
loading: addHookName(loading, 'Loading State'),
194
error: addHookName(error, 'Error State'),
195
refetch: addHookName(refetch, 'Refetch Function')
196
};
197
}
198
199
// Component using named hooks
200
function UserProfile({ userId }: { userId: number }) {
201
const { data: user, loading, error, refetch } = useApiData<User>(`/api/users/${userId}`);
202
const counter = useCounter(0, 1);
203
204
if (loading) return createElement("div", null, "Loading...");
205
if (error) return createElement("div", null, `Error: ${error.message}`);
206
if (!user) return createElement("div", null, "User not found");
207
208
return createElement("div", null,
209
createElement("h2", null, user.name),
210
createElement("p", null, user.email),
211
createElement("div", null,
212
createElement("p", null, `Counter: ${counter.count}`),
213
createElement("button", { onClick: counter.increment }, "+"),
214
createElement("button", { onClick: counter.decrement }, "-"),
215
createElement("button", { onClick: counter.reset }, "Reset")
216
),
217
createElement("button", { onClick: refetch }, "Refetch User")
218
);
219
}
220
```
221
222
### Global Options and Hooks
223
224
Global configuration options for development and debugging.
225
226
```typescript { .api }
227
/**
228
* Global options object for customizing Preact behavior
229
*/
230
interface Options {
231
/** Hook invoked when a VNode is created */
232
vnode?(vnode: VNode): void;
233
234
/** Hook invoked before a VNode is unmounted */
235
unmount?(vnode: VNode): void;
236
237
/** Hook invoked after a VNode has been rendered/updated */
238
diffed?(vnode: VNode): void;
239
240
/** Event processing hook */
241
event?(e: Event): any;
242
243
/** Custom requestAnimationFrame implementation */
244
requestAnimationFrame?(callback: () => void): void;
245
246
/** Custom render batching function */
247
debounceRendering?(cb: () => void): void;
248
249
/** Custom debug value formatter */
250
useDebugValue?(value: string | number): void;
251
252
/** Internal hook name tracking */
253
_addHookName?(name: string | number): void;
254
255
/** Suspense resolution hook */
256
__suspenseDidResolve?(vnode: VNode, cb: () => void): void;
257
258
/** Custom attribute serialization for precompiled JSX */
259
attr?(name: string, value: any): string | void;
260
}
261
262
/**
263
* Global options object - can be modified to customize Preact behavior
264
*/
265
const options: Options;
266
```
267
268
**Usage Examples:**
269
270
```typescript
271
import { options } from "preact";
272
import { createElement } from "preact";
273
274
// Development logging setup
275
if (process.env.NODE_ENV === 'development') {
276
// Log all VNode creations
277
const originalVnode = options.vnode;
278
options.vnode = (vnode) => {
279
console.log('VNode created:', vnode.type, vnode.props);
280
originalVnode?.(vnode);
281
};
282
283
// Log component unmounts
284
const originalUnmount = options.unmount;
285
options.unmount = (vnode) => {
286
console.log('VNode unmounting:', vnode.type);
287
originalUnmount?.(vnode);
288
};
289
290
// Log render completions
291
const originalDiffed = options.diffed;
292
options.diffed = (vnode) => {
293
console.log('VNode diffed:', vnode.type);
294
originalDiffed?.(vnode);
295
};
296
297
// Custom debug value formatting
298
options.useDebugValue = (value) => {
299
if (typeof value === 'object') {
300
return JSON.stringify(value, null, 2);
301
}
302
return String(value);
303
};
304
}
305
306
// Performance monitoring
307
function setupPerformanceMonitoring() {
308
const renderTimes = new Map<string, number>();
309
310
const originalVnode = options.vnode;
311
options.vnode = (vnode) => {
312
if (typeof vnode.type === 'function') {
313
const componentName = vnode.type.displayName || vnode.type.name || 'Anonymous';
314
renderTimes.set(componentName, performance.now());
315
}
316
originalVnode?.(vnode);
317
};
318
319
const originalDiffed = options.diffed;
320
options.diffed = (vnode) => {
321
if (typeof vnode.type === 'function') {
322
const componentName = vnode.type.displayName || vnode.type.name || 'Anonymous';
323
const startTime = renderTimes.get(componentName);
324
325
if (startTime) {
326
const renderTime = performance.now() - startTime;
327
console.log(`${componentName} rendered in ${renderTime.toFixed(2)}ms`);
328
renderTimes.delete(componentName);
329
}
330
}
331
originalDiffed?.(vnode);
332
};
333
}
334
335
// Custom event handling
336
function setupEventLogging() {
337
const originalEvent = options.event;
338
options.event = (e) => {
339
// Log all events in development
340
if (process.env.NODE_ENV === 'development') {
341
console.log('Event:', e.type, e.target);
342
}
343
344
// Call original event handler if it exists
345
return originalEvent?.(e);
346
};
347
}
348
349
// Custom render batching for testing
350
function setupTestBatching() {
351
options.debounceRendering = (callback) => {
352
// Immediate rendering for tests
353
callback();
354
};
355
}
356
357
// Component that uses global options
358
function DebuggedApp() {
359
useEffect(() => {
360
if (process.env.NODE_ENV === 'development') {
361
setupPerformanceMonitoring();
362
setupEventLogging();
363
}
364
}, []);
365
366
return createElement("div", null,
367
createElement("h1", null, "Debugged Application"),
368
createElement("button", {
369
onClick: () => console.log("Button clicked")
370
}, "Test Button")
371
);
372
}
373
```
374
375
### Development Environment Setup
376
377
Utilities and patterns for setting up effective development environments.
378
379
**Usage Examples:**
380
381
```typescript
382
// Development environment configuration
383
function setupDevelopmentEnvironment() {
384
// Auto-import debug module in development
385
if (process.env.NODE_ENV === 'development') {
386
import('preact/debug').then(() => {
387
console.log('Preact debug mode enabled');
388
});
389
390
// Import devtools integration
391
import('preact/devtools').then(() => {
392
console.log('React DevTools integration enabled');
393
});
394
}
395
}
396
397
// Error boundary with debug information
398
class DebugErrorBoundary extends Component<
399
{ children: ComponentChildren; fallback?: ComponentChildren },
400
{ hasError: boolean; error?: Error }
401
> {
402
state = { hasError: false };
403
404
static getDerivedStateFromError(error: Error) {
405
return { hasError: true, error };
406
}
407
408
componentDidCatch(error: Error, errorInfo: any) {
409
console.error('Error caught by boundary:', error);
410
console.error('Error info:', errorInfo);
411
412
if (process.env.NODE_ENV === 'development') {
413
const currentVNode = getCurrentVNode();
414
if (currentVNode) {
415
console.error('Error in component:', getDisplayName(currentVNode));
416
console.error('Component stack:', getOwnerStack(currentVNode));
417
}
418
}
419
}
420
421
render() {
422
if (this.state.hasError) {
423
return this.props.fallback || createElement("div", null,
424
createElement("h2", null, "Something went wrong"),
425
process.env.NODE_ENV === 'development' &&
426
createElement("pre", null, this.state.error?.stack)
427
);
428
}
429
430
return this.props.children;
431
}
432
}
433
434
// Development-only component inspector
435
function ComponentInspector({ children }: { children: ComponentChildren }) {
436
if (process.env.NODE_ENV !== 'development') {
437
return children;
438
}
439
440
return createElement("dev-tools", {
441
style: { position: 'relative' }
442
},
443
children,
444
createElement(DebugInfo)
445
);
446
}
447
```