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
```