0
# React Compatibility
1
2
Complete React compatibility layer enabling seamless migration from React applications with advanced features like memo, forwardRef, Suspense, and portals. The `preact/compat` module provides a drop-in replacement for React.
3
4
## Capabilities
5
6
### Enhanced Components
7
8
Advanced component patterns and higher-order components for performance optimization and component composition.
9
10
```typescript { .api }
11
/**
12
* React version compatibility string
13
*/
14
const version: string; // "18.3.1"
15
16
/**
17
* No-op component that passes through children (alias for Fragment)
18
*/
19
const StrictMode: FunctionComponent<{ children?: ComponentChildren }>;
20
21
/**
22
* Component with automatic shallow comparison of props and state
23
*/
24
abstract class PureComponent<P = {}, S = {}> extends Component<P, S> {
25
// Automatically implements shouldComponentUpdate with shallow comparison
26
}
27
28
/**
29
* Higher-order component that memoizes component rendering
30
* @param component - Component to memoize
31
* @param propsAreEqual - Optional custom comparison function
32
* @returns Memoized component
33
*/
34
function memo<P extends object>(
35
component: FunctionComponent<P>,
36
propsAreEqual?: (prevProps: Readonly<P>, nextProps: Readonly<P>) => boolean
37
): FunctionComponent<P>;
38
39
/**
40
* Forwards refs to child components
41
* @param render - Render function that receives props and ref
42
* @returns Component that forwards refs
43
*/
44
function forwardRef<T, P = {}>(
45
render: (props: P, ref: Ref<T>) => ComponentChildren
46
): ComponentType<P & RefAttributes<T>>;
47
48
interface RefAttributes<T> {
49
ref?: Ref<T>;
50
}
51
```
52
53
**Usage Examples:**
54
55
```typescript
56
import { PureComponent, memo, forwardRef, createElement } from "preact/compat";
57
58
// PureComponent automatically optimizes re-renders
59
class UserCard extends PureComponent<{ user: { name: string; email: string } }> {
60
render() {
61
const { user } = this.props;
62
return createElement("div", { className: "user-card" },
63
createElement("h3", null, user.name),
64
createElement("p", null, user.email)
65
);
66
}
67
// No need to implement shouldComponentUpdate - automatically does shallow comparison
68
}
69
70
// Memoized functional component
71
const ExpensiveComponent = memo<{ data: any[]; processing?: boolean }>(
72
({ data, processing }) => {
73
const processedData = data.map(item => ({ ...item, processed: true }));
74
75
return createElement("div", null,
76
processing && createElement("div", null, "Processing..."),
77
createElement("ul", null,
78
processedData.map((item, index) =>
79
createElement("li", { key: index }, JSON.stringify(item))
80
)
81
)
82
);
83
},
84
// Custom comparison function
85
(prevProps, nextProps) => {
86
return prevProps.data.length === nextProps.data.length &&
87
prevProps.processing === nextProps.processing;
88
}
89
);
90
91
// ForwardRef for exposing child component methods
92
interface InputHandle {
93
focus(): void;
94
getValue(): string;
95
}
96
97
const FancyInput = forwardRef<InputHandle, { placeholder?: string }>((props, ref) => {
98
const inputRef = useRef<HTMLInputElement>(null);
99
100
useImperativeHandle(ref, () => ({
101
focus: () => inputRef.current?.focus(),
102
getValue: () => inputRef.current?.value || ''
103
}));
104
105
return createElement("input", {
106
ref: inputRef,
107
placeholder: props.placeholder,
108
className: "fancy-input"
109
});
110
});
111
112
// Using forwardRef component
113
function ParentComponent() {
114
const inputRef = useRef<InputHandle>(null);
115
116
return createElement("div", null,
117
createElement(FancyInput, { ref: inputRef, placeholder: "Enter text" }),
118
createElement("button", {
119
onClick: () => {
120
inputRef.current?.focus();
121
console.log(inputRef.current?.getValue());
122
}
123
}, "Focus & Log Value")
124
);
125
}
126
```
127
128
### Suspense and Lazy Loading
129
130
Components and utilities for handling asynchronous loading and code splitting.
131
132
```typescript { .api }
133
/**
134
* Lazy-loaded component wrapper
135
* @param factory - Function that returns a Promise resolving to a component
136
* @returns Lazy component that can be used with Suspense
137
*/
138
function lazy<P extends ComponentProps<any>>(
139
factory: () => Promise<{ default: ComponentType<P> }>
140
): ComponentType<P>;
141
142
/**
143
* Suspense boundary for handling loading states
144
*/
145
const Suspense: ComponentType<{
146
children: ComponentChildren;
147
fallback?: ComponentChildren;
148
}>;
149
150
/**
151
* Experimental component for coordinating multiple Suspense boundaries
152
*/
153
const SuspenseList: ComponentType<{
154
children: ComponentChildren;
155
revealOrder?: "forwards" | "backwards" | "together";
156
tail?: "collapsed" | "hidden";
157
}>;
158
```
159
160
**Usage Examples:**
161
162
```typescript
163
import { lazy, Suspense, createElement } from "preact/compat";
164
165
// Lazy loaded components
166
const LazyUserProfile = lazy(() => import("./UserProfile"));
167
const LazyDashboard = lazy(() => import("./Dashboard"));
168
169
function App() {
170
const [currentView, setCurrentView] = useState("profile");
171
172
return createElement("div", null,
173
createElement("nav", null,
174
createElement("button", {
175
onClick: () => setCurrentView("profile")
176
}, "Profile"),
177
createElement("button", {
178
onClick: () => setCurrentView("dashboard")
179
}, "Dashboard")
180
),
181
182
createElement(Suspense, {
183
fallback: createElement("div", null, "Loading...")
184
},
185
currentView === "profile"
186
? createElement(LazyUserProfile, { userId: 123 })
187
: createElement(LazyDashboard)
188
)
189
);
190
}
191
192
// Multiple lazy components with SuspenseList
193
function MultiComponentView() {
194
return createElement(Suspense, {
195
fallback: createElement("div", null, "Loading all components...")
196
},
197
createElement(SuspenseList, { revealOrder: "forwards" },
198
createElement(Suspense, {
199
fallback: createElement("div", null, "Loading header...")
200
},
201
createElement(LazyHeader)
202
),
203
createElement(Suspense, {
204
fallback: createElement("div", null, "Loading content...")
205
},
206
createElement(LazyContent)
207
),
208
createElement(Suspense, {
209
fallback: createElement("div", null, "Loading footer...")
210
},
211
createElement(LazyFooter)
212
)
213
)
214
);
215
}
216
```
217
218
### DOM Utilities
219
220
Utilities for DOM manipulation and component lifecycle management.
221
222
```typescript { .api }
223
/**
224
* Renders components into a different DOM subtree
225
* @param children - Components to render
226
* @param container - DOM element to render into
227
* @returns Portal VNode
228
*/
229
function createPortal(children: ComponentChildren, container: Element): VNode;
230
231
/**
232
* Removes a component tree from a DOM container
233
* @param container - DOM container to unmount from
234
* @returns True if component was unmounted
235
*/
236
function unmountComponentAtNode(container: Element): boolean;
237
238
/**
239
* Gets the DOM node for a component instance
240
* @param component - Component instance or DOM element
241
* @returns DOM element or null
242
*/
243
function findDOMNode(component: Component<any> | Element | null): Element | null;
244
245
/**
246
* Creates a factory function for creating elements of a specific type (legacy)
247
* @param type - Component or element type
248
* @returns Factory function that creates elements of the specified type
249
*/
250
function createFactory<P>(type: ComponentType<P> | string): (props?: P, ...children: ComponentChildren[]) => VNode<P>;
251
```
252
253
**Usage Examples:**
254
255
```typescript
256
import { createPortal, unmountComponentAtNode, findDOMNode, createElement } from "preact/compat";
257
258
// Portal for rendering modals
259
function Modal({ isOpen, onClose, children }: {
260
isOpen: boolean;
261
onClose: () => void;
262
children: ComponentChildren;
263
}) {
264
if (!isOpen) return null;
265
266
const modalRoot = document.getElementById("modal-root")!;
267
268
return createPortal(
269
createElement("div", { className: "modal-overlay", onClick: onClose },
270
createElement("div", {
271
className: "modal-content",
272
onClick: (e) => e.stopPropagation()
273
},
274
createElement("button", {
275
className: "modal-close",
276
onClick: onClose
277
}, "×"),
278
children
279
)
280
),
281
modalRoot
282
);
283
}
284
285
// Using the modal
286
function App() {
287
const [isModalOpen, setIsModalOpen] = useState(false);
288
289
return createElement("div", null,
290
createElement("button", {
291
onClick: () => setIsModalOpen(true)
292
}, "Open Modal"),
293
294
createElement(Modal, {
295
isOpen: isModalOpen,
296
onClose: () => setIsModalOpen(false)
297
},
298
createElement("h2", null, "Modal Content"),
299
createElement("p", null, "This is rendered in a portal!")
300
)
301
);
302
}
303
304
// Cleanup utility
305
function ComponentManager() {
306
const containerRef = useRef<HTMLDivElement>(null);
307
308
const cleanupComponent = () => {
309
if (containerRef.current) {
310
const wasUnmounted = unmountComponentAtNode(containerRef.current);
311
console.log("Component unmounted:", wasUnmounted);
312
}
313
};
314
315
return createElement("div", null,
316
createElement("div", { ref: containerRef }),
317
createElement("button", { onClick: cleanupComponent }, "Cleanup")
318
);
319
}
320
321
// findDOMNode usage (use with caution in modern code)
322
class LegacyComponent extends Component {
323
focusInput() {
324
const domNode = findDOMNode(this);
325
if (domNode instanceof Element) {
326
const input = domNode.querySelector("input");
327
input?.focus();
328
}
329
}
330
331
render() {
332
return createElement("div", null,
333
createElement("input", { type: "text" }),
334
createElement("button", {
335
onClick: () => this.focusInput()
336
}, "Focus Input")
337
);
338
}
339
}
340
```
341
342
### Update Batching and Scheduling
343
344
Functions for controlling update timing and batching behavior.
345
346
```typescript { .api }
347
/**
348
* Synchronously flushes updates (no-op in Preact)
349
* @param callback - Function to execute synchronously
350
* @returns The return value of the callback
351
*/
352
function flushSync<R>(callback: () => R): R;
353
354
/**
355
* Manually batches state updates
356
* @param callback - Function containing state updates to batch
357
*/
358
function unstable_batchedUpdates(callback: () => void): void;
359
360
/**
361
* Marks updates as non-urgent (no-op in Preact)
362
* @param callback - Function containing non-urgent updates
363
*/
364
function startTransition(callback: () => void): void;
365
```
366
367
**Usage Examples:**
368
369
```typescript
370
import { flushSync, unstable_batchedUpdates, startTransition, useState, createElement } from "preact/compat";
371
372
function BatchingExample() {
373
const [count, setCount] = useState(0);
374
const [name, setName] = useState("");
375
376
const handleMultipleUpdates = () => {
377
// These updates are automatically batched in modern Preact
378
setCount(c => c + 1);
379
setName("Updated");
380
381
// Force synchronous update (rarely needed)
382
flushSync(() => {
383
setCount(c => c + 1);
384
});
385
386
// Explicit batching (mostly for compatibility)
387
unstable_batchedUpdates(() => {
388
setCount(c => c + 1);
389
setName("Batched Update");
390
});
391
392
// Non-urgent updates (no-op in Preact)
393
startTransition(() => {
394
setCount(c => c + 1);
395
});
396
};
397
398
return createElement("div", null,
399
createElement("p", null, `Count: ${count}`),
400
createElement("p", null, `Name: ${name}`),
401
createElement("button", { onClick: handleMultipleUpdates }, "Update All")
402
);
403
}
404
```
405
406
### Enhanced Children API
407
408
Extended utilities for manipulating and working with component children.
409
410
```typescript { .api }
411
/**
412
* Enhanced children utilities
413
*/
414
const Children: {
415
/**
416
* Maps over children with a function
417
* @param children - Children to map over
418
* @param fn - Function to apply to each child
419
* @returns Array of mapped children
420
*/
421
map(children: ComponentChildren, fn: (child: ComponentChild, index: number) => ComponentChild): ComponentChild[];
422
423
/**
424
* Iterates over children with a function
425
* @param children - Children to iterate over
426
* @param fn - Function to call for each child
427
*/
428
forEach(children: ComponentChildren, fn: (child: ComponentChild, index: number) => void): void;
429
430
/**
431
* Counts the number of children
432
* @param children - Children to count
433
* @returns Number of children
434
*/
435
count(children: ComponentChildren): number;
436
437
/**
438
* Ensures children contains exactly one child
439
* @param children - Children to validate
440
* @returns The single child
441
* @throws Error if not exactly one child
442
*/
443
only(children: ComponentChildren): ComponentChild;
444
445
/**
446
* Converts children to an array
447
* @param children - Children to convert
448
* @returns Array of children
449
*/
450
toArray(children: ComponentChildren): ComponentChild[];
451
};
452
453
/**
454
* Additional element type checking utilities
455
*/
456
function isValidElement(element: any): element is VNode;
457
function isFragment(element: any): boolean;
458
function isMemo(element: any): boolean;
459
```
460
461
**Usage Examples:**
462
463
```typescript
464
import { Children, isValidElement, isFragment, createElement } from "preact/compat";
465
466
// Using Children utilities
467
function ChildrenProcessor({ children }: { children: ComponentChildren }) {
468
const childCount = Children.count(children);
469
470
const processedChildren = Children.map(children, (child, index) => {
471
if (isValidElement(child)) {
472
// Add index prop to valid elements
473
return createElement(child.type, {
474
...child.props,
475
key: child.key || index,
476
"data-index": index
477
});
478
}
479
return child;
480
});
481
482
return createElement("div", null,
483
createElement("p", null, `Processing ${childCount} children`),
484
processedChildren
485
);
486
}
487
488
// Validation utilities
489
function SafeContainer({ children }: { children: ComponentChildren }) {
490
Children.forEach(children, (child, index) => {
491
if (isValidElement(child)) {
492
console.log(`Child ${index} is a valid element:`, child.type);
493
} else if (isFragment(child)) {
494
console.log(`Child ${index} is a fragment`);
495
} else {
496
console.log(`Child ${index} is primitive:`, child);
497
}
498
});
499
500
return createElement("div", null, children);
501
}
502
503
// Single child validation
504
function SingleChildWrapper({ children }: { children: ComponentChildren }) {
505
try {
506
const singleChild = Children.only(children);
507
return createElement("div", { className: "single-wrapper" }, singleChild);
508
} catch (error) {
509
return createElement("div", { className: "error" },
510
"This component requires exactly one child"
511
);
512
}
513
}
514
```
515
516
### React 18 Compatibility Hooks
517
518
Modern React 18 hooks for concurrent features and external store synchronization.
519
520
```typescript { .api }
521
/**
522
* Insertion effect that runs before layout effects (alias for useLayoutEffect)
523
* @param effect - Effect function
524
* @param deps - Dependency array
525
*/
526
function useInsertionEffect(effect: EffectCallback, deps?: DependencyList): void;
527
528
/**
529
* Returns transition state and function for non-urgent updates
530
* @returns Tuple of isPending (always false) and startTransition function
531
*/
532
function useTransition(): [false, typeof startTransition];
533
534
/**
535
* Returns a deferred value for performance optimization (pass-through in Preact)
536
* @param value - Value to defer
537
* @returns The same value (no deferring in Preact)
538
*/
539
function useDeferredValue<T>(value: T): T;
540
541
/**
542
* Synchronizes with external store state
543
* @param subscribe - Function to subscribe to store changes
544
* @param getSnapshot - Function to get current store state
545
* @param getServerSnapshot - Optional server-side snapshot function
546
* @returns Current store state
547
*/
548
function useSyncExternalStore<T>(
549
subscribe: (onStoreChange: () => void) => () => void,
550
getSnapshot: () => T,
551
getServerSnapshot?: () => T
552
): T;
553
```
554
555
**Usage Examples:**
556
557
```typescript
558
import {
559
useInsertionEffect,
560
useTransition,
561
useDeferredValue,
562
useSyncExternalStore,
563
useState,
564
createElement
565
} from "preact/compat";
566
567
// useInsertionEffect for critical DOM mutations
568
function CriticalStyleInjector() {
569
useInsertionEffect(() => {
570
// Insert critical styles before any layout effects
571
const style = document.createElement('style');
572
style.textContent = '.critical { color: red; }';
573
document.head.appendChild(style);
574
575
return () => {
576
document.head.removeChild(style);
577
};
578
}, []);
579
580
return createElement("div", { className: "critical" }, "Critical content");
581
}
582
583
// useTransition for non-urgent updates
584
function TransitionExample() {
585
const [query, setQuery] = useState("");
586
const [results, setResults] = useState([]);
587
const [isPending, startTransition] = useTransition();
588
589
const handleSearch = (e: Event) => {
590
const value = (e.target as HTMLInputElement).value;
591
setQuery(value);
592
593
// Mark expensive search as non-urgent
594
startTransition(() => {
595
// Expensive search operation
596
const searchResults = performExpensiveSearch(value);
597
setResults(searchResults);
598
});
599
};
600
601
return createElement("div", null,
602
createElement("input", {
603
value: query,
604
onChange: handleSearch,
605
placeholder: "Search..."
606
}),
607
isPending && createElement("div", null, "Searching..."),
608
createElement("ul", null,
609
results.map((result, index) =>
610
createElement("li", { key: index }, result)
611
)
612
)
613
);
614
}
615
616
// useDeferredValue for performance optimization
617
function DeferredExample() {
618
const [input, setInput] = useState("");
619
const deferredInput = useDeferredValue(input);
620
621
return createElement("div", null,
622
createElement("input", {
623
value: input,
624
onChange: (e) => setInput((e.target as HTMLInputElement).value)
625
}),
626
createElement(ExpensiveComponent, { query: deferredInput })
627
);
628
}
629
630
// useSyncExternalStore for external state
631
function ExternalStoreExample() {
632
// External store (e.g., a simple observable)
633
const store = {
634
state: { count: 0 },
635
listeners: new Set<() => void>(),
636
637
subscribe(listener: () => void) {
638
this.listeners.add(listener);
639
return () => this.listeners.delete(listener);
640
},
641
642
getSnapshot() {
643
return this.state;
644
},
645
646
increment() {
647
this.state = { count: this.state.count + 1 };
648
this.listeners.forEach(listener => listener());
649
}
650
};
651
652
const state = useSyncExternalStore(
653
store.subscribe.bind(store),
654
store.getSnapshot.bind(store)
655
);
656
657
return createElement("div", null,
658
createElement("p", null, `External count: ${state.count}`),
659
createElement("button", {
660
onClick: () => store.increment()
661
}, "Increment External Store")
662
);
663
}
664
```