or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

compat.mdcomponents.mdcontext.mdcore.mddevtools.mdhooks.mdindex.mdjsx-runtime.mdtesting.md

compat.mddocs/

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

```