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

hooks.mddocs/

0

# Hooks API

1

2

Modern React hooks for state management, side effects, and component logic in functional components. Preact's hooks implementation provides complete compatibility with React hooks.

3

4

## Capabilities

5

6

### State Management Hooks

7

8

Hooks for managing local component state using functional programming patterns.

9

10

```typescript { .api }

11

/**

12

* Manages local component state

13

* @param initialState - Initial state value or lazy initializer function

14

* @returns Tuple of current state and state setter function

15

*/

16

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];

17

function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];

18

19

/**

20

* Manages state using reducer pattern

21

* @param reducer - Reducer function that takes state and action

22

* @param initialArg - Initial state or argument for init function

23

* @param init - Optional lazy initializer function

24

* @returns Tuple of current state and dispatch function

25

*/

26

function useReducer<R extends Reducer<any, any>>(

27

reducer: R,

28

initialState: ReducerState<R>,

29

initializer?: undefined

30

): [ReducerState<R>, Dispatch<ReducerAction<R>>];

31

32

function useReducer<R extends Reducer<any, any>, I>(

33

reducer: R,

34

initialArg: I,

35

initializer: (arg: I) => ReducerState<R>

36

): [ReducerState<R>, Dispatch<ReducerAction<R>>];

37

38

// Supporting types

39

type Dispatch<A> = (value: A) => void;

40

type SetStateAction<S> = S | ((prevState: S) => S);

41

type Reducer<S, A> = (prevState: S, action: A) => S;

42

type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any> ? S : never;

43

type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;

44

```

45

46

**Usage Examples:**

47

48

```typescript

49

import { useState, useReducer, createElement } from "preact/hooks";

50

51

// Basic useState

52

function Counter() {

53

const [count, setCount] = useState(0);

54

55

return createElement("div", null,

56

createElement("p", null, `Count: ${count}`),

57

createElement("button", {

58

onClick: () => setCount(count + 1)

59

}, "Increment"),

60

createElement("button", {

61

onClick: () => setCount(prev => prev - 1)

62

}, "Decrement")

63

);

64

}

65

66

// useState with lazy initialization

67

function ExpensiveComponent() {

68

const [data, setData] = useState(() => {

69

// Expensive computation only runs once

70

return computeExpensiveValue();

71

});

72

73

return createElement("div", null, JSON.stringify(data));

74

}

75

76

// useReducer for complex state logic

77

interface State {

78

count: number;

79

step: number;

80

}

81

82

type Action =

83

| { type: 'increment' }

84

| { type: 'decrement' }

85

| { type: 'setStep'; step: number }

86

| { type: 'reset' };

87

88

function reducer(state: State, action: Action): State {

89

switch (action.type) {

90

case 'increment':

91

return { ...state, count: state.count + state.step };

92

case 'decrement':

93

return { ...state, count: state.count - state.step };

94

case 'setStep':

95

return { ...state, step: action.step };

96

case 'reset':

97

return { count: 0, step: 1 };

98

default:

99

return state;

100

}

101

}

102

103

function AdvancedCounter() {

104

const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });

105

106

return createElement("div", null,

107

createElement("p", null, `Count: ${state.count} (step: ${state.step})`),

108

createElement("button", {

109

onClick: () => dispatch({ type: 'increment' })

110

}, "Increment"),

111

createElement("button", {

112

onClick: () => dispatch({ type: 'decrement' })

113

}, "Decrement"),

114

createElement("input", {

115

type: "number",

116

value: state.step,

117

onChange: (e) => dispatch({

118

type: 'setStep',

119

step: parseInt((e.target as HTMLInputElement).value) || 1

120

})

121

}),

122

createElement("button", {

123

onClick: () => dispatch({ type: 'reset' })

124

}, "Reset")

125

);

126

}

127

```

128

129

### Effect Hooks

130

131

Hooks for managing side effects and lifecycle events in functional components.

132

133

```typescript { .api }

134

/**

135

* Performs side effects after render

136

* @param effect - Effect function, optionally returns cleanup function

137

* @param deps - Optional dependency array to control when effect runs

138

*/

139

function useEffect(effect: EffectCallback, deps?: DependencyList): void;

140

141

/**

142

* Performs side effects synchronously before browser paint

143

* @param effect - Effect function, optionally returns cleanup function

144

* @param deps - Optional dependency array to control when effect runs

145

*/

146

function useLayoutEffect(effect: EffectCallback, deps?: DependencyList): void;

147

148

// Supporting types

149

type EffectCallback = () => (void | (() => void | undefined));

150

type DependencyList = ReadonlyArray<any>;

151

```

152

153

**Usage Examples:**

154

155

```typescript

156

import { useEffect, useLayoutEffect, useState, createElement } from "preact/hooks";

157

158

// Basic effect with cleanup

159

function Timer() {

160

const [time, setTime] = useState(new Date());

161

162

useEffect(() => {

163

const interval = setInterval(() => {

164

setTime(new Date());

165

}, 1000);

166

167

// Cleanup function

168

return () => clearInterval(interval);

169

}, []); // Empty deps array means effect runs once on mount

170

171

return createElement("div", null, time.toLocaleTimeString());

172

}

173

174

// Effect with dependencies

175

function UserProfile({ userId }: { userId: number }) {

176

const [user, setUser] = useState(null);

177

const [loading, setLoading] = useState(true);

178

179

useEffect(() => {

180

let cancelled = false;

181

182

async function fetchUser() {

183

setLoading(true);

184

try {

185

const response = await fetch(`/api/users/${userId}`);

186

const userData = await response.json();

187

188

if (!cancelled) {

189

setUser(userData);

190

setLoading(false);

191

}

192

} catch (error) {

193

if (!cancelled) {

194

setUser(null);

195

setLoading(false);

196

}

197

}

198

}

199

200

fetchUser();

201

202

return () => {

203

cancelled = true;

204

};

205

}, [userId]); // Re-run when userId changes

206

207

if (loading) return createElement("div", null, "Loading...");

208

if (!user) return createElement("div", null, "User not found");

209

210

return createElement("div", null,

211

createElement("h1", null, user.name),

212

createElement("p", null, user.email)

213

);

214

}

215

216

// useLayoutEffect for DOM measurements

217

function MeasuredComponent() {

218

const [width, setWidth] = useState(0);

219

const [ref, setRef] = useState<HTMLDivElement | null>(null);

220

221

useLayoutEffect(() => {

222

if (ref) {

223

const updateWidth = () => {

224

setWidth(ref.offsetWidth);

225

};

226

227

updateWidth();

228

window.addEventListener('resize', updateWidth);

229

230

return () => {

231

window.removeEventListener('resize', updateWidth);

232

};

233

}

234

}, [ref]);

235

236

return createElement("div",

237

{ ref: setRef },

238

`Width: ${width}px`

239

);

240

}

241

```

242

243

### Reference Hooks

244

245

Hooks for creating persistent references and customizing ref behavior.

246

247

```typescript { .api }

248

/**

249

* Creates a mutable ref object that persists across renders

250

* @param initialValue - Initial value for the ref

251

* @returns Mutable ref object with current property

252

*/

253

function useRef<T>(initialValue: T): MutableRefObject<T>;

254

function useRef<T>(initialValue: T | null): RefObject<T>;

255

function useRef<T = undefined>(): MutableRefObject<T | undefined>;

256

257

/**

258

* Customizes the instance value exposed by a ref

259

* @param ref - Ref to customize

260

* @param createHandle - Function that returns the custom instance

261

* @param deps - Optional dependency array

262

*/

263

function useImperativeHandle<T, R extends T>(

264

ref: Ref<T> | undefined,

265

init: () => R,

266

deps?: DependencyList

267

): void;

268

269

// Supporting types

270

interface MutableRefObject<T> {

271

current: T;

272

}

273

274

interface RefObject<T> {

275

readonly current: T | null;

276

}

277

```

278

279

**Usage Examples:**

280

281

```typescript

282

import { useRef, useImperativeHandle, forwardRef, createElement } from "preact/hooks";

283

284

// Basic ref usage

285

function TextInput() {

286

const inputRef = useRef<HTMLInputElement>(null);

287

288

const focusInput = () => {

289

if (inputRef.current) {

290

inputRef.current.focus();

291

}

292

};

293

294

return createElement("div", null,

295

createElement("input", { ref: inputRef, type: "text" }),

296

createElement("button", { onClick: focusInput }, "Focus Input")

297

);

298

}

299

300

// Ref for storing mutable values

301

function PreviousValue({ value }: { value: number }) {

302

const prevValueRef = useRef<number>();

303

304

useEffect(() => {

305

prevValueRef.current = value;

306

});

307

308

const prevValue = prevValueRef.current;

309

310

return createElement("div", null,

311

createElement("p", null, `Current: ${value}`),

312

createElement("p", null, `Previous: ${prevValue}`)

313

);

314

}

315

316

// Custom imperative handle

317

interface CustomInputHandle {

318

focus: () => void;

319

getValue: () => string;

320

setValue: (value: string) => void;

321

}

322

323

const CustomInput = forwardRef<CustomInputHandle, { placeholder?: string }>((props, ref) => {

324

const inputRef = useRef<HTMLInputElement>(null);

325

326

useImperativeHandle(ref, () => ({

327

focus: () => inputRef.current?.focus(),

328

getValue: () => inputRef.current?.value || '',

329

setValue: (value: string) => {

330

if (inputRef.current) {

331

inputRef.current.value = value;

332

}

333

}

334

}), []);

335

336

return createElement("input", {

337

ref: inputRef,

338

type: "text",

339

placeholder: props.placeholder

340

});

341

});

342

343

// Using the custom input

344

function ParentComponent() {

345

const customInputRef = useRef<CustomInputHandle>(null);

346

347

const handleButtonClick = () => {

348

if (customInputRef.current) {

349

customInputRef.current.focus();

350

customInputRef.current.setValue("Hello World");

351

}

352

};

353

354

return createElement("div", null,

355

createElement(CustomInput, { ref: customInputRef, placeholder: "Enter text" }),

356

createElement("button", { onClick: handleButtonClick }, "Set Value & Focus")

357

);

358

}

359

```

360

361

### Performance Hooks

362

363

Hooks for optimizing component performance through memoization.

364

365

```typescript { .api }

366

/**

367

* Memoizes a computed value, recalculating only when dependencies change

368

* @param factory - Function that computes the memoized value

369

* @param deps - Dependency array that triggers recalculation

370

* @returns Memoized value

371

*/

372

function useMemo<T>(factory: () => T, deps: DependencyList): T;

373

374

/**

375

* Memoizes a callback function, recreating only when dependencies change

376

* @param callback - Function to memoize

377

* @param deps - Dependency array that triggers recreation

378

* @returns Memoized callback function

379

*/

380

function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;

381

```

382

383

**Usage Examples:**

384

385

```typescript

386

import { useMemo, useCallback, useState, createElement } from "preact/hooks";

387

388

// useMemo for expensive calculations

389

function ExpensiveList({ items }: { items: number[] }) {

390

const [filter, setFilter] = useState("");

391

392

const filteredAndSortedItems = useMemo(() => {

393

console.log("Recalculating filtered items"); // Only logs when items or filter change

394

395

return items

396

.filter(item => item.toString().includes(filter))

397

.sort((a, b) => a - b);

398

}, [items, filter]);

399

400

return createElement("div", null,

401

createElement("input", {

402

value: filter,

403

onChange: (e) => setFilter((e.target as HTMLInputElement).value),

404

placeholder: "Filter items"

405

}),

406

createElement("ul", null,

407

filteredAndSortedItems.map(item =>

408

createElement("li", { key: item }, item)

409

)

410

)

411

);

412

}

413

414

// useCallback for stable function references

415

interface TodoItemProps {

416

todo: { id: number; text: string; completed: boolean };

417

onToggle: (id: number) => void;

418

onDelete: (id: number) => void;

419

}

420

421

const TodoItem = ({ todo, onToggle, onDelete }: TodoItemProps) => {

422

return createElement("li", null,

423

createElement("input", {

424

type: "checkbox",

425

checked: todo.completed,

426

onChange: () => onToggle(todo.id)

427

}),

428

createElement("span", null, todo.text),

429

createElement("button", { onClick: () => onDelete(todo.id) }, "Delete")

430

);

431

};

432

433

function TodoList() {

434

const [todos, setTodos] = useState([

435

{ id: 1, text: "Learn Preact", completed: false },

436

{ id: 2, text: "Build an app", completed: false }

437

]);

438

439

// These callbacks won't cause TodoItem re-renders when todos change

440

const handleToggle = useCallback((id: number) => {

441

setTodos(prev => prev.map(todo =>

442

todo.id === id ? { ...todo, completed: !todo.completed } : todo

443

));

444

}, []);

445

446

const handleDelete = useCallback((id: number) => {

447

setTodos(prev => prev.filter(todo => todo.id !== id));

448

}, []);

449

450

return createElement("ul", null,

451

todos.map(todo =>

452

createElement(TodoItem, {

453

key: todo.id,

454

todo,

455

onToggle: handleToggle,

456

onDelete: handleDelete

457

})

458

)

459

);

460

}

461

```

462

463

### Context and Utility Hooks

464

465

Additional hooks for context consumption, debugging, and error handling.

466

467

```typescript { .api }

468

/**

469

* Consumes a context value from the nearest provider

470

* @param context - Context object created by createContext

471

* @returns Current context value

472

*/

473

function useContext<T>(context: Context<T>): T;

474

475

/**

476

* Generates a unique ID string for accessibility attributes

477

* @returns Unique string identifier

478

*/

479

function useId(): string;

480

481

/**

482

* Displays a custom label and value in React DevTools

483

* @param value - Value to display

484

* @param format - Optional formatter function

485

*/

486

function useDebugValue<T>(value: T): void;

487

function useDebugValue<T>(value: T, format: (value: T) => any): void;

488

489

/**

490

* Provides error boundary functionality for functional components

491

* @param onError - Optional error handler callback

492

* @returns Function to reset error state

493

*/

494

function useErrorBoundary(onError?: (error: Error, errorInfo: ErrorInfo) => void): (error?: Error) => void;

495

```

496

497

**Usage Examples:**

498

499

```typescript

500

import {

501

useContext,

502

useId,

503

useDebugValue,

504

useErrorBoundary,

505

createContext,

506

useState,

507

createElement

508

} from "preact/hooks";

509

510

// Context usage

511

const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} });

512

513

function ThemedButton() {

514

const { theme, toggleTheme } = useContext(ThemeContext);

515

516

return createElement("button", {

517

onClick: toggleTheme,

518

style: {

519

backgroundColor: theme === 'light' ? '#fff' : '#333',

520

color: theme === 'light' ? '#333' : '#fff'

521

}

522

}, `Current theme: ${theme}`);

523

}

524

525

// useId for accessibility

526

function FormField({ label }: { label: string }) {

527

const fieldId = useId();

528

const helpId = useId();

529

530

return createElement("div", null,

531

createElement("label", { htmlFor: fieldId }, label),

532

createElement("input", {

533

id: fieldId,

534

"aria-describedby": helpId

535

}),

536

createElement("div", { id: helpId }, "Help text for this field")

537

);

538

}

539

540

// Custom hook with debug value

541

function useLocalStorage<T>(key: string, initialValue: T) {

542

const [storedValue, setStoredValue] = useState<T>(() => {

543

try {

544

const item = window.localStorage.getItem(key);

545

return item ? JSON.parse(item) : initialValue;

546

} catch (error) {

547

return initialValue;

548

}

549

});

550

551

// Show the key and current value in DevTools

552

useDebugValue(`${key}: ${JSON.stringify(storedValue)}`);

553

554

const setValue = (value: T | ((val: T) => T)) => {

555

try {

556

const valueToStore = value instanceof Function ? value(storedValue) : value;

557

setStoredValue(valueToStore);

558

window.localStorage.setItem(key, JSON.stringify(valueToStore));

559

} catch (error) {

560

console.error(error);

561

}

562

};

563

564

return [storedValue, setValue] as const;

565

}

566

567

// Error boundary hook

568

function ErrorProneComponent({ shouldError }: { shouldError: boolean }) {

569

const resetError = useErrorBoundary((error, errorInfo) => {

570

console.error("Component error:", error, errorInfo);

571

});

572

573

if (shouldError) {

574

// This will trigger the error boundary

575

throw new Error("Something went wrong!");

576

}

577

578

return createElement("div", null,

579

createElement("p", null, "Component is working fine"),

580

createElement("button", { onClick: () => resetError() }, "Reset Error State")

581

);

582

}

583

```