0
# Context API
1
2
Provider/consumer pattern for sharing data across component hierarchies without prop drilling. Preact's Context API provides efficient state management for deeply nested components.
3
4
## Capabilities
5
6
### Context Creation
7
8
Creates context objects that enable data sharing between components at different levels of the component tree.
9
10
```typescript { .api }
11
/**
12
* Creates a new context with a default value
13
* @param defaultValue - Default value used when no Provider is found
14
* @returns Context object with Provider and Consumer components
15
*/
16
function createContext<T>(defaultValue: T): Context<T>;
17
18
/**
19
* Context object interface
20
*/
21
interface Context<T> {
22
Provider: ComponentType<ProviderProps<T>>;
23
Consumer: ComponentType<ConsumerProps<T>>;
24
displayName?: string;
25
}
26
27
interface ProviderProps<T> {
28
value: T;
29
children?: ComponentChildren;
30
}
31
32
interface ConsumerProps<T> {
33
children: (value: T) => ComponentChildren;
34
}
35
36
// Type aliases for compatibility
37
interface PreactContext<T> extends Context<T> {}
38
interface PreactProvider<T> extends Provider<T> {}
39
interface PreactConsumer<T> extends Consumer<T> {}
40
41
/**
42
* Extract context value type from Context
43
*/
44
type ContextType<C extends Context<any>> = C extends Context<infer T> ? T : never;
45
```
46
47
**Usage Examples:**
48
49
```typescript
50
import { createContext, createElement, useState } from "preact";
51
import { useContext } from "preact/hooks";
52
53
// Theme context
54
interface ThemeContextValue {
55
theme: 'light' | 'dark';
56
toggleTheme: () => void;
57
}
58
59
const ThemeContext = createContext<ThemeContextValue>({
60
theme: 'light',
61
toggleTheme: () => {}
62
});
63
64
// Theme provider component
65
function ThemeProvider({ children }: { children: ComponentChildren }) {
66
const [theme, setTheme] = useState<'light' | 'dark'>('light');
67
68
const toggleTheme = () => {
69
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
70
};
71
72
const value = { theme, toggleTheme };
73
74
return createElement(ThemeContext.Provider, { value }, children);
75
}
76
77
// Component using context with hook
78
function ThemedButton() {
79
const { theme, toggleTheme } = useContext(ThemeContext);
80
81
return createElement("button", {
82
onClick: toggleTheme,
83
style: {
84
backgroundColor: theme === 'light' ? '#fff' : '#333',
85
color: theme === 'light' ? '#333' : '#fff',
86
border: `1px solid ${theme === 'light' ? '#333' : '#fff'}`
87
}
88
}, `Switch to ${theme === 'light' ? 'dark' : 'light'} theme`);
89
}
90
91
// Component using context with Consumer
92
function ThemedText() {
93
return createElement(ThemeContext.Consumer, null, ({ theme }) =>
94
createElement("p", {
95
style: {
96
color: theme === 'light' ? '#333' : '#fff'
97
}
98
}, `Current theme: ${theme}`)
99
);
100
}
101
102
// App with theme provider
103
function App() {
104
return createElement(ThemeProvider, null,
105
createElement("div", null,
106
createElement("h1", null, "Themed App"),
107
createElement(ThemedButton),
108
createElement(ThemedText)
109
)
110
);
111
}
112
```
113
114
### Provider Component
115
116
Component that provides context values to descendant components.
117
118
```typescript { .api }
119
/**
120
* Provider component interface
121
*/
122
interface Provider<T> extends FunctionComponent<{
123
value: T;
124
children?: ComponentChildren;
125
}> {}
126
127
/**
128
* Provider component that makes context value available to descendants
129
* Automatically included in Context object from createContext
130
*/
131
```
132
133
**Usage Examples:**
134
135
```typescript
136
import { createContext, createElement, useState } from "preact";
137
138
// User authentication context
139
interface User {
140
id: number;
141
name: string;
142
email: string;
143
}
144
145
interface AuthContextValue {
146
user: User | null;
147
login: (user: User) => void;
148
logout: () => void;
149
isAuthenticated: boolean;
150
}
151
152
const AuthContext = createContext<AuthContextValue>({
153
user: null,
154
login: () => {},
155
logout: () => {},
156
isAuthenticated: false
157
});
158
159
// Authentication provider
160
function AuthProvider({ children }: { children: ComponentChildren }) {
161
const [user, setUser] = useState<User | null>(null);
162
163
const login = (userData: User) => {
164
setUser(userData);
165
localStorage.setItem('user', JSON.stringify(userData));
166
};
167
168
const logout = () => {
169
setUser(null);
170
localStorage.removeItem('user');
171
};
172
173
const contextValue: AuthContextValue = {
174
user,
175
login,
176
logout,
177
isAuthenticated: user !== null
178
};
179
180
return createElement(AuthContext.Provider, { value: contextValue }, children);
181
}
182
183
// Multiple nested providers
184
function AppProviders({ children }: { children: ComponentChildren }) {
185
return createElement(AuthProvider, null,
186
createElement(ThemeProvider, null,
187
createElement(LocaleProvider, null,
188
children
189
)
190
)
191
);
192
}
193
194
// Provider with complex state management
195
interface CartItem {
196
id: number;
197
name: string;
198
price: number;
199
quantity: number;
200
}
201
202
interface CartContextValue {
203
items: CartItem[];
204
addItem: (item: Omit<CartItem, 'quantity'>) => void;
205
removeItem: (id: number) => void;
206
updateQuantity: (id: number, quantity: number) => void;
207
total: number;
208
itemCount: number;
209
}
210
211
const CartContext = createContext<CartContextValue>({
212
items: [],
213
addItem: () => {},
214
removeItem: () => {},
215
updateQuantity: () => {},
216
total: 0,
217
itemCount: 0
218
});
219
220
function CartProvider({ children }: { children: ComponentChildren }) {
221
const [items, setItems] = useState<CartItem[]>([]);
222
223
const addItem = (newItem: Omit<CartItem, 'quantity'>) => {
224
setItems(prevItems => {
225
const existingItem = prevItems.find(item => item.id === newItem.id);
226
if (existingItem) {
227
return prevItems.map(item =>
228
item.id === newItem.id
229
? { ...item, quantity: item.quantity + 1 }
230
: item
231
);
232
}
233
return [...prevItems, { ...newItem, quantity: 1 }];
234
});
235
};
236
237
const removeItem = (id: number) => {
238
setItems(prevItems => prevItems.filter(item => item.id !== id));
239
};
240
241
const updateQuantity = (id: number, quantity: number) => {
242
if (quantity <= 0) {
243
removeItem(id);
244
return;
245
}
246
247
setItems(prevItems =>
248
prevItems.map(item =>
249
item.id === id ? { ...item, quantity } : item
250
)
251
);
252
};
253
254
const total = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
255
const itemCount = items.reduce((count, item) => count + item.quantity, 0);
256
257
const contextValue: CartContextValue = {
258
items,
259
addItem,
260
removeItem,
261
updateQuantity,
262
total,
263
itemCount
264
};
265
266
return createElement(CartContext.Provider, { value: contextValue }, children);
267
}
268
```
269
270
### Consumer Component
271
272
Component that consumes context values using render props pattern.
273
274
```typescript { .api }
275
/**
276
* Consumer component interface
277
*/
278
interface Consumer<T> extends FunctionComponent<{
279
children: (value: T) => ComponentChildren;
280
}> {}
281
282
/**
283
* Consumer component that provides context value to render function
284
* Automatically included in Context object from createContext
285
*/
286
```
287
288
**Usage Examples:**
289
290
```typescript
291
import { createContext, createElement } from "preact";
292
293
// Using Consumer with render props
294
function UserProfile() {
295
return createElement(AuthContext.Consumer, null, ({ user, isAuthenticated }) => {
296
if (!isAuthenticated) {
297
return createElement("div", null, "Please log in");
298
}
299
300
return createElement("div", { className: "user-profile" },
301
createElement("h2", null, user.name),
302
createElement("p", null, user.email),
303
createElement("img", {
304
src: `https://api.dicebear.com/7.x/initials/svg?seed=${user.name}`,
305
alt: `${user.name}'s avatar`
306
})
307
);
308
});
309
}
310
311
// Consumer with conditional rendering
312
function ShoppingCartIcon() {
313
return createElement(CartContext.Consumer, null, ({ itemCount }) =>
314
createElement("div", { className: "cart-icon" },
315
createElement("span", null, "๐"),
316
itemCount > 0 && createElement("span", { className: "badge" }, itemCount)
317
)
318
);
319
}
320
321
// Nested consumers
322
function UserDashboard() {
323
return createElement(AuthContext.Consumer, null, ({ user, isAuthenticated }) =>
324
createElement(ThemeContext.Consumer, null, ({ theme }) =>
325
createElement(CartContext.Consumer, null, ({ total, itemCount }) => {
326
if (!isAuthenticated) {
327
return createElement("div", null, "Access denied");
328
}
329
330
return createElement("div", {
331
className: `dashboard theme-${theme}`
332
},
333
createElement("h1", null, `Welcome, ${user.name}!`),
334
createElement("p", null, `Cart: ${itemCount} items ($${total.toFixed(2)})`),
335
createElement("p", null, `Theme: ${theme}`)
336
);
337
})
338
)
339
);
340
}
341
342
// Consumer with error handling
343
function SafeConsumer() {
344
return createElement(AuthContext.Consumer, null, (contextValue) => {
345
try {
346
if (!contextValue) {
347
return createElement("div", null, "Context not available");
348
}
349
350
const { user, isAuthenticated } = contextValue;
351
352
return createElement("div", null,
353
isAuthenticated
354
? `Hello, ${user?.name}`
355
: "Not logged in"
356
);
357
} catch (error) {
358
return createElement("div", null, "Error loading user data");
359
}
360
});
361
}
362
```
363
364
### Context Best Practices
365
366
Patterns and utilities for effective context usage.
367
368
```typescript { .api }
369
/**
370
* Custom hook pattern for context consumption
371
* Provides better error handling and type safety
372
*/
373
function useCustomContext<T>(context: Context<T>, errorMessage?: string): T {
374
const contextValue = useContext(context);
375
376
if (contextValue === undefined) {
377
throw new Error(errorMessage || 'useCustomContext must be used within a Provider');
378
}
379
380
return contextValue;
381
}
382
```
383
384
**Usage Examples:**
385
386
```typescript
387
import { createContext, createElement, useContext } from "preact";
388
389
// Custom hook for safer context consumption
390
function useAuth() {
391
const context = useContext(AuthContext);
392
393
if (!context) {
394
throw new Error('useAuth must be used within an AuthProvider');
395
}
396
397
return context;
398
}
399
400
function useTheme() {
401
const context = useContext(ThemeContext);
402
403
if (!context) {
404
throw new Error('useTheme must be used within a ThemeProvider');
405
}
406
407
return context;
408
}
409
410
// Context with reducer pattern
411
interface AppState {
412
user: User | null;
413
theme: 'light' | 'dark';
414
locale: string;
415
}
416
417
type AppAction =
418
| { type: 'SET_USER'; user: User | null }
419
| { type: 'TOGGLE_THEME' }
420
| { type: 'SET_LOCALE'; locale: string };
421
422
function appReducer(state: AppState, action: AppAction): AppState {
423
switch (action.type) {
424
case 'SET_USER':
425
return { ...state, user: action.user };
426
case 'TOGGLE_THEME':
427
return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
428
case 'SET_LOCALE':
429
return { ...state, locale: action.locale };
430
default:
431
return state;
432
}
433
}
434
435
interface AppContextValue {
436
state: AppState;
437
dispatch: (action: AppAction) => void;
438
}
439
440
const AppContext = createContext<AppContextValue | null>(null);
441
442
function AppProvider({ children }: { children: ComponentChildren }) {
443
const [state, dispatch] = useReducer(appReducer, {
444
user: null,
445
theme: 'light',
446
locale: 'en'
447
});
448
449
return createElement(AppContext.Provider, {
450
value: { state, dispatch }
451
}, children);
452
}
453
454
function useApp() {
455
const context = useContext(AppContext);
456
457
if (!context) {
458
throw new Error('useApp must be used within an AppProvider');
459
}
460
461
return context;
462
}
463
464
// Component using the app context
465
function AppHeader() {
466
const { state, dispatch } = useApp();
467
468
return createElement("header", {
469
className: `header theme-${state.theme}`
470
},
471
createElement("h1", null, "My App"),
472
state.user && createElement("span", null, `Welcome, ${state.user.name}`),
473
createElement("button", {
474
onClick: () => dispatch({ type: 'TOGGLE_THEME' })
475
}, `Switch to ${state.theme === 'light' ? 'dark' : 'light'} theme`)
476
);
477
}
478
479
// Context composition pattern
480
function Providers({ children }: { children: ComponentChildren }) {
481
return createElement(AppProvider, null,
482
createElement(AuthProvider, null,
483
createElement(CartProvider, null,
484
children
485
)
486
)
487
);
488
}
489
```