0
# Component System
1
2
Class-based and functional component patterns with complete lifecycle methods and state management capabilities. Preact supports both React-style class components and modern functional components.
3
4
## Capabilities
5
6
### Component Base Class
7
8
Base class for creating stateful components with lifecycle methods and local state management.
9
10
```typescript { .api }
11
/**
12
* Base class for stateful components
13
*/
14
abstract class Component<P = {}, S = {}> {
15
constructor(props?: P, context?: any);
16
17
// Static properties
18
static displayName?: string;
19
static defaultProps?: any;
20
static contextType?: Context<any>;
21
22
// Static lifecycle methods
23
static getDerivedStateFromProps?(props: Readonly<object>, state: Readonly<object>): object | null;
24
static getDerivedStateFromError?(error: any): object | null;
25
26
// Instance properties
27
state: Readonly<S>;
28
props: RenderableProps<P>;
29
context: any;
30
base?: Element | Text;
31
32
// State management
33
setState<K extends keyof S>(
34
state: ((prevState: Readonly<S>, props: Readonly<P>) => Pick<S, K> | Partial<S> | null) |
35
(Pick<S, K> | Partial<S> | null),
36
callback?: () => void
37
): void;
38
39
forceUpdate(callback?: () => void): void;
40
41
// Lifecycle methods (optional)
42
componentWillMount?(): void;
43
componentDidMount?(): void;
44
componentWillUnmount?(): void;
45
componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void;
46
shouldComponentUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: any): boolean;
47
componentWillUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: any): void;
48
getSnapshotBeforeUpdate?(oldProps: Readonly<P>, oldState: Readonly<S>): any;
49
componentDidUpdate?(previousProps: Readonly<P>, previousState: Readonly<S>, snapshot: any): void;
50
componentDidCatch?(error: any, errorInfo: ErrorInfo): void;
51
getChildContext?(): object;
52
53
// Abstract render method
54
abstract render(props?: RenderableProps<P>, state?: Readonly<S>, context?: any): ComponentChildren;
55
}
56
57
interface ErrorInfo {
58
componentStack?: string;
59
}
60
```
61
62
**Usage Examples:**
63
64
```typescript
65
import { Component, createElement } from "preact";
66
67
// Basic class component
68
class Counter extends Component<{}, { count: number }> {
69
state = { count: 0 };
70
71
increment = () => {
72
this.setState({ count: this.state.count + 1 });
73
};
74
75
render() {
76
return createElement("div", null,
77
createElement("p", null, `Count: ${this.state.count}`),
78
createElement("button", { onClick: this.increment }, "Increment")
79
);
80
}
81
}
82
83
// Component with props and lifecycle
84
interface TimerProps {
85
interval: number;
86
onTick: (count: number) => void;
87
}
88
89
interface TimerState {
90
count: number;
91
}
92
93
class Timer extends Component<TimerProps, TimerState> {
94
private intervalId?: number;
95
96
constructor(props: TimerProps) {
97
super(props);
98
this.state = { count: 0 };
99
}
100
101
componentDidMount() {
102
this.intervalId = window.setInterval(() => {
103
this.setState(prevState => ({ count: prevState.count + 1 }), () => {
104
this.props.onTick(this.state.count);
105
});
106
}, this.props.interval);
107
}
108
109
componentWillUnmount() {
110
if (this.intervalId) {
111
clearInterval(this.intervalId);
112
}
113
}
114
115
shouldComponentUpdate(nextProps: TimerProps, nextState: TimerState) {
116
return nextState.count !== this.state.count || nextProps.interval !== this.props.interval;
117
}
118
119
render() {
120
return createElement("div", null, `Timer: ${this.state.count}`);
121
}
122
}
123
```
124
125
### Functional Components
126
127
Function-based components for stateless presentation and modern hook-based logic.
128
129
```typescript { .api }
130
/**
131
* Functional component interface
132
*/
133
interface FunctionComponent<P = {}> {
134
(props: RenderableProps<P>, context?: any): ComponentChildren;
135
displayName?: string;
136
defaultProps?: Partial<P> | undefined;
137
}
138
139
// Type alias
140
interface FunctionalComponent<P = {}> extends FunctionComponent<P> {}
141
142
// Generic component type
143
type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>;
144
145
// Component constructor interface
146
interface ComponentClass<P = {}, S = {}> {
147
new (props: P, context?: any): Component<P, S>;
148
displayName?: string;
149
defaultProps?: Partial<P>;
150
contextType?: Context<any>;
151
getDerivedStateFromProps?(props: Readonly<P>, state: Readonly<S>): Partial<S> | null;
152
getDerivedStateFromError?(error: any): Partial<S> | null;
153
}
154
```
155
156
**Usage Examples:**
157
158
```typescript
159
import { createElement, FunctionComponent } from "preact";
160
161
// Basic functional component
162
function Greeting({ name }: { name: string }) {
163
return createElement("h1", null, `Hello ${name}!`);
164
}
165
166
// Functional component with default props
167
const Button: FunctionComponent<{ text?: string; onClick?: () => void }> = ({
168
text = "Click me",
169
onClick
170
}) => {
171
return createElement("button", { onClick }, text);
172
};
173
Button.defaultProps = { text: "Default Text" };
174
175
// Component with children
176
interface CardProps {
177
title: string;
178
children?: ComponentChildren;
179
}
180
181
const Card: FunctionComponent<CardProps> = ({ title, children }) => {
182
return createElement("div", { className: "card" },
183
createElement("h2", { className: "card-title" }, title),
184
createElement("div", { className: "card-content" }, children)
185
);
186
};
187
188
// Using the card component
189
const app = createElement(Card, { title: "My Card" },
190
createElement("p", null, "This is the content"),
191
createElement("button", null, "Action")
192
);
193
```
194
195
### Component Lifecycle
196
197
Detailed lifecycle methods available in class components for managing component behavior throughout its existence.
198
199
```typescript { .api }
200
/**
201
* Component lifecycle methods
202
*/
203
interface Component<P = {}, S = {}> {
204
// Mounting phase
205
componentWillMount?(): void;
206
componentDidMount?(): void;
207
208
// Updating phase
209
componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void;
210
shouldComponentUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: any): boolean;
211
componentWillUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: any): void;
212
getSnapshotBeforeUpdate?(oldProps: Readonly<P>, oldState: Readonly<S>): any;
213
componentDidUpdate?(previousProps: Readonly<P>, previousState: Readonly<S>, snapshot: any): void;
214
215
// Unmounting phase
216
componentWillUnmount?(): void;
217
218
// Error handling
219
componentDidCatch?(error: any, errorInfo: ErrorInfo): void;
220
221
// Context
222
getChildContext?(): object;
223
}
224
```
225
226
**Usage Examples:**
227
228
```typescript
229
import { Component, createElement } from "preact";
230
231
class LifecycleDemo extends Component<{ userId: number }, { user: any; loading: boolean }> {
232
state = { user: null, loading: true };
233
234
async componentDidMount() {
235
console.log("Component mounted");
236
await this.fetchUser(this.props.userId);
237
}
238
239
componentWillReceiveProps(nextProps: { userId: number }) {
240
if (nextProps.userId !== this.props.userId) {
241
this.setState({ loading: true });
242
this.fetchUser(nextProps.userId);
243
}
244
}
245
246
shouldComponentUpdate(nextProps: { userId: number }, nextState: any) {
247
return nextProps.userId !== this.props.userId ||
248
nextState.user !== this.state.user ||
249
nextState.loading !== this.state.loading;
250
}
251
252
getSnapshotBeforeUpdate() {
253
return { scrollTop: document.documentElement.scrollTop };
254
}
255
256
componentDidUpdate(prevProps: { userId: number }, prevState: any, snapshot: any) {
257
console.log("Component updated", { prevProps, snapshot });
258
}
259
260
componentWillUnmount() {
261
console.log("Component will unmount");
262
// Cleanup subscriptions, timers, etc.
263
}
264
265
componentDidCatch(error: any, errorInfo: any) {
266
console.error("Component caught error:", error, errorInfo);
267
this.setState({ user: null, loading: false });
268
}
269
270
private async fetchUser(userId: number) {
271
try {
272
const response = await fetch(`/api/users/${userId}`);
273
const user = await response.json();
274
this.setState({ user, loading: false });
275
} catch (error) {
276
this.setState({ user: null, loading: false });
277
}
278
}
279
280
render() {
281
if (this.state.loading) {
282
return createElement("div", null, "Loading...");
283
}
284
285
if (!this.state.user) {
286
return createElement("div", null, "User not found");
287
}
288
289
return createElement("div", null,
290
createElement("h1", null, this.state.user.name),
291
createElement("p", null, this.state.user.email)
292
);
293
}
294
}
295
```
296
297
### Component Props and State Types
298
299
Type definitions for component properties, state, and related interfaces.
300
301
```typescript { .api }
302
/**
303
* Props with children and ref support
304
*/
305
type RenderableProps<P, RefType = any> = P &
306
Readonly<Attributes & { children?: ComponentChildren; ref?: Ref<RefType> }>;
307
308
/**
309
* Props for specific component types
310
*/
311
type ComponentProps<C extends ComponentType<any> | keyof JSXInternal.IntrinsicElements> =
312
C extends ComponentType<infer P>
313
? P
314
: C extends keyof JSXInternal.IntrinsicElements
315
? JSXInternal.IntrinsicElements[C]
316
: {};
317
318
/**
319
* Base attributes interface
320
*/
321
interface Attributes {
322
key?: Key | undefined;
323
jsx?: boolean | undefined;
324
}
325
326
/**
327
* Attributes with ref support
328
*/
329
interface ClassAttributes<T> extends Attributes {
330
ref?: Ref<T>;
331
}
332
333
/**
334
* General component type union
335
*/
336
type AnyComponent<P = {}, S = {}> = FunctionComponent<P> | ComponentConstructor<P, S>;
337
338
interface ComponentConstructor<P = {}, S = {}> extends ComponentClass<P, S> {}
339
```
340
341
**Usage Examples:**
342
343
```typescript
344
import { ComponentProps, RenderableProps, createElement } from "preact";
345
346
// Using ComponentProps to extract props from existing components
347
type ButtonProps = ComponentProps<"button">;
348
type CustomButtonProps = ComponentProps<typeof CustomButton>;
349
350
// Component with properly typed props
351
interface UserCardProps {
352
user: {
353
id: number;
354
name: string;
355
email: string;
356
};
357
onEdit?: (id: number) => void;
358
}
359
360
function UserCard({ user, onEdit, children }: RenderableProps<UserCardProps>) {
361
return createElement("div", { className: "user-card" },
362
createElement("h3", null, user.name),
363
createElement("p", null, user.email),
364
onEdit && createElement("button",
365
{ onClick: () => onEdit(user.id) },
366
"Edit"
367
),
368
children
369
);
370
}
371
372
// Generic component with constraints
373
function List<T>({
374
items,
375
renderItem,
376
keyExtractor
377
}: RenderableProps<{
378
items: T[];
379
renderItem: (item: T, index: number) => ComponentChildren;
380
keyExtractor: (item: T, index: number) => Key;
381
}>) {
382
return createElement("ul", null,
383
items.map((item, index) =>
384
createElement("li",
385
{ key: keyExtractor(item, index) },
386
renderItem(item, index)
387
)
388
)
389
);
390
}
391
```