0
# Context and Scoping
1
2
Context API for passing data through the component tree and scoping utilities for managing reactive ownership and cleanup.
3
4
## Capabilities
5
6
### Context Creation and Usage
7
8
Create and consume context for passing data through component trees without prop drilling.
9
10
```typescript { .api }
11
/**
12
* Creates a Context to handle state scoped for component children
13
* @param defaultValue - Default value for the context
14
* @returns Context object with Provider component
15
*/
16
function createContext<T>(defaultValue?: T): Context<T>;
17
18
/**
19
* Uses a context to receive scoped state from parent's Context.Provider
20
* @param context - Context object to consume
21
* @returns Current context value
22
*/
23
function useContext<T>(context: Context<T>): T;
24
25
interface Context<T> {
26
id: symbol;
27
Provider: ContextProviderComponent<T>;
28
defaultValue: T;
29
}
30
31
type ContextProviderComponent<T> = FlowComponent<{ value: T }>;
32
```
33
34
**Usage Examples:**
35
36
```typescript
37
import { createContext, useContext, createSignal, ParentComponent } from "solid-js";
38
39
// Create theme context
40
interface Theme {
41
primary: string;
42
secondary: string;
43
mode: "light" | "dark";
44
}
45
46
const ThemeContext = createContext<Theme>({
47
primary: "#007bff",
48
secondary: "#6c757d",
49
mode: "light"
50
});
51
52
// Theme provider component
53
const ThemeProvider: ParentComponent<{ theme: Theme }> = (props) => {
54
return (
55
<ThemeContext.Provider value={props.theme}>
56
{props.children}
57
</ThemeContext.Provider>
58
);
59
};
60
61
// Component that uses theme context
62
function ThemedButton(props: { children: JSX.Element; onClick?: () => void }) {
63
const theme = useContext(ThemeContext);
64
65
return (
66
<button
67
style={{
68
"background-color": theme.primary,
69
color: theme.mode === "dark" ? "white" : "black",
70
border: `1px solid ${theme.secondary}`
71
}}
72
onClick={props.onClick}
73
>
74
{props.children}
75
</button>
76
);
77
}
78
79
// App with theme context
80
function App() {
81
const [isDark, setIsDark] = createSignal(false);
82
83
const theme = () => ({
84
primary: isDark() ? "#bb86fc" : "#007bff",
85
secondary: isDark() ? "#03dac6" : "#6c757d",
86
mode: isDark() ? "dark" as const : "light" as const
87
});
88
89
return (
90
<ThemeProvider theme={theme()}>
91
<div style={{
92
"background-color": isDark() ? "#121212" : "#ffffff",
93
"min-height": "100vh",
94
padding: "20px"
95
}}>
96
<h1>Themed App</h1>
97
<ThemedButton onClick={() => setIsDark(!isDark())}>
98
Toggle {isDark() ? "Light" : "Dark"} Mode
99
</ThemedButton>
100
</div>
101
</ThemeProvider>
102
);
103
}
104
```
105
106
### Advanced Context Patterns
107
108
Create multiple contexts and nested providers for complex state management.
109
110
**Usage Examples:**
111
112
```typescript
113
import { createContext, useContext, createSignal, createMemo } from "solid-js";
114
115
// User context
116
interface User {
117
id: number;
118
name: string;
119
role: "admin" | "user";
120
}
121
122
const UserContext = createContext<User | null>(null);
123
124
// Settings context
125
interface Settings {
126
language: string;
127
notifications: boolean;
128
theme: "light" | "dark";
129
}
130
131
const SettingsContext = createContext<Settings>({
132
language: "en",
133
notifications: true,
134
theme: "light"
135
});
136
137
// Combined app context provider
138
function AppProviders(props: { children: JSX.Element }) {
139
const [user, setUser] = createSignal<User | null>(null);
140
const [settings, setSettings] = createSignal<Settings>({
141
language: "en",
142
notifications: true,
143
theme: "light"
144
});
145
146
// Login function available through context
147
const userActions = {
148
login: (userData: User) => setUser(userData),
149
logout: () => setUser(null),
150
updateSettings: (newSettings: Partial<Settings>) =>
151
setSettings(prev => ({ ...prev, ...newSettings }))
152
};
153
154
return (
155
<UserContext.Provider value={user()}>
156
<SettingsContext.Provider value={settings()}>
157
{/* We can also create an actions context */}
158
<ActionsContext.Provider value={userActions}>
159
{props.children}
160
</ActionsContext.Provider>
161
</SettingsContext.Provider>
162
</UserContext.Provider>
163
);
164
}
165
166
// Custom hooks for easier context consumption
167
function useUser() {
168
const user = useContext(UserContext);
169
const isAuthenticated = createMemo(() => user !== null);
170
const isAdmin = createMemo(() => user?.role === "admin");
171
172
return { user, isAuthenticated: isAuthenticated(), isAdmin: isAdmin() };
173
}
174
175
function useSettings() {
176
return useContext(SettingsContext);
177
}
178
179
// Component using multiple contexts
180
function UserDashboard() {
181
const { user, isAuthenticated, isAdmin } = useUser();
182
const settings = useSettings();
183
const actions = useContext(ActionsContext);
184
185
return (
186
<div>
187
<Show when={isAuthenticated} fallback={<LoginForm />}>
188
<div>
189
<h1>Welcome, {user!.name}!</h1>
190
<p>Language: {settings.language}</p>
191
<p>Theme: {settings.theme}</p>
192
193
<Show when={isAdmin}>
194
<AdminPanel />
195
</Show>
196
197
<button onClick={actions.logout}>Logout</button>
198
</div>
199
</Show>
200
</div>
201
);
202
}
203
```
204
205
### Reactive Ownership and Scoping
206
207
Manage reactive ownership and create isolated reactive scopes.
208
209
```typescript { .api }
210
/**
211
* Creates a new non-tracked reactive context that doesn't auto-dispose
212
* @param fn - Function to run in the new reactive scope
213
* @param detachedOwner - Optional owner to detach from
214
* @returns Return value of the function
215
*/
216
function createRoot<T>(
217
fn: (dispose: () => void) => T,
218
detachedOwner?: Owner
219
): T;
220
221
/**
222
* Gets the current reactive owner
223
* @returns Current owner or null
224
*/
225
function getOwner(): Owner | null;
226
227
/**
228
* Runs a function with a specific reactive owner
229
* @param owner - Owner to run the function with
230
* @param fn - Function to run
231
* @returns Return value of the function or undefined
232
*/
233
function runWithOwner<T>(
234
owner: Owner | null,
235
fn: () => T
236
): T | undefined;
237
238
interface Owner {
239
owned: Computation<any>[] | null;
240
cleanups: (() => void)[] | null;
241
owner: Owner | null;
242
context: any | null;
243
sourceMap?: SourceMapValue[];
244
name?: string;
245
}
246
```
247
248
**Usage Examples:**
249
250
```typescript
251
import { createRoot, createSignal, createEffect, getOwner, runWithOwner } from "solid-js";
252
253
// Creating isolated reactive scopes
254
function IsolatedComponent() {
255
let dispose: (() => void) | undefined;
256
257
const cleanup = () => {
258
if (dispose) {
259
dispose();
260
dispose = undefined;
261
}
262
};
263
264
const initialize = () => {
265
cleanup(); // Clean up previous scope if it exists
266
267
createRoot((disposeScope) => {
268
dispose = disposeScope;
269
270
const [count, setCount] = createSignal(0);
271
272
createEffect(() => {
273
console.log("Isolated count:", count());
274
});
275
276
// Set up some interval that will be cleaned up
277
const interval = setInterval(() => {
278
setCount(c => c + 1);
279
}, 1000);
280
281
onCleanup(() => {
282
clearInterval(interval);
283
console.log("Isolated scope cleaned up");
284
});
285
286
return { count, setCount };
287
});
288
};
289
290
onCleanup(cleanup);
291
292
return (
293
<div>
294
<button onClick={initialize}>Initialize Isolated Scope</button>
295
<button onClick={cleanup}>Cleanup Scope</button>
296
</div>
297
);
298
}
299
300
// Working with owners for advanced patterns
301
function OwnerExample() {
302
const [items, setItems] = createSignal<{ id: number; owner: Owner | null }[]>([]);
303
304
const addItem = () => {
305
const owner = getOwner();
306
setItems(prev => [...prev, { id: Date.now(), owner }]);
307
};
308
309
const runInOriginalOwner = (item: { id: number; owner: Owner | null }) => {
310
runWithOwner(item.owner, () => {
311
console.log("Running in original owner context");
312
// This runs in the context where the item was created
313
});
314
};
315
316
return (
317
<div>
318
<button onClick={addItem}>Add Item</button>
319
<For each={items()}>
320
{(item) => (
321
<div>
322
Item {item.id}
323
<button onClick={() => runInOriginalOwner(item)}>
324
Run in Original Context
325
</button>
326
</div>
327
)}
328
</For>
329
</div>
330
);
331
}
332
```
333
334
### Transitions and Scheduling
335
336
Manage reactive updates with transitions and custom scheduling.
337
338
```typescript { .api }
339
/**
340
* Wraps updates in a low-priority transition
341
* @param fn - Function containing updates to run in transition
342
* @returns Promise that resolves when transition completes
343
*/
344
function startTransition(fn: () => void): Promise<void>;
345
346
/**
347
* Returns a tuple with pending accessor and startTransition function
348
* @returns Tuple of [isPending accessor, startTransition function]
349
*/
350
function useTransition(): [Accessor<boolean>, (fn: () => void) => Promise<void>];
351
```
352
353
**Usage Examples:**
354
355
```typescript
356
import { useTransition, createSignal, For, createMemo } from "solid-js";
357
358
function TransitionExample() {
359
const [query, setQuery] = createSignal("");
360
const [items, setItems] = createSignal(generateItems(1000));
361
const [isPending, startTransition] = useTransition();
362
363
// Expensive filtering operation
364
const filteredItems = createMemo(() => {
365
const q = query().toLowerCase();
366
return items().filter(item =>
367
item.name.toLowerCase().includes(q) ||
368
item.description.toLowerCase().includes(q)
369
);
370
});
371
372
const handleSearch = (value: string) => {
373
// Update query immediately for responsive input
374
setQuery(value);
375
376
// Wrap expensive filtering in transition
377
startTransition(() => {
378
// This could trigger expensive computations
379
console.log("Filtering items...");
380
});
381
};
382
383
return (
384
<div>
385
<input
386
type="text"
387
placeholder="Search items..."
388
value={query()}
389
onInput={(e) => handleSearch(e.target.value)}
390
/>
391
392
<div>
393
{isPending() && <div class="loading">Filtering...</div>}
394
<p>Found {filteredItems().length} items</p>
395
</div>
396
397
<div class="items">
398
<For each={filteredItems()}>
399
{(item) => (
400
<div class="item">
401
<h3>{item.name}</h3>
402
<p>{item.description}</p>
403
</div>
404
)}
405
</For>
406
</div>
407
</div>
408
);
409
}
410
411
function generateItems(count: number) {
412
return Array.from({ length: count }, (_, i) => ({
413
id: i,
414
name: `Item ${i}`,
415
description: `Description for item ${i}`
416
}));
417
}
418
```
419
420
### Global State Management with Context
421
422
Create global state management using context and reactive primitives.
423
424
**Usage Examples:**
425
426
```typescript
427
import { createContext, useContext, createSignal, createMemo } from "solid-js";
428
429
// Global store interface
430
interface Store {
431
user: User | null;
432
cart: CartItem[];
433
theme: Theme;
434
}
435
436
interface StoreActions {
437
setUser: (user: User | null) => void;
438
addToCart: (item: Product) => void;
439
removeFromCart: (itemId: number) => void;
440
setTheme: (theme: Theme) => void;
441
}
442
443
// Create store context
444
const StoreContext = createContext<Store & StoreActions>();
445
446
// Store provider
447
function StoreProvider(props: { children: JSX.Element }) {
448
const [user, setUser] = createSignal<User | null>(null);
449
const [cart, setCart] = createSignal<CartItem[]>([]);
450
const [theme, setTheme] = createSignal<Theme>({ mode: "light" });
451
452
const addToCart = (product: Product) => {
453
setCart(prev => {
454
const existing = prev.find(item => item.product.id === product.id);
455
if (existing) {
456
return prev.map(item =>
457
item.product.id === product.id
458
? { ...item, quantity: item.quantity + 1 }
459
: item
460
);
461
}
462
return [...prev, { product, quantity: 1 }];
463
});
464
};
465
466
const removeFromCart = (productId: number) => {
467
setCart(prev => prev.filter(item => item.product.id !== productId));
468
};
469
470
const store = createMemo(() => ({
471
user: user(),
472
cart: cart(),
473
theme: theme(),
474
setUser,
475
addToCart,
476
removeFromCart,
477
setTheme
478
}));
479
480
return (
481
<StoreContext.Provider value={store()}>
482
{props.children}
483
</StoreContext.Provider>
484
);
485
}
486
487
// Custom hook to use store
488
function useStore() {
489
const store = useContext(StoreContext);
490
if (!store) {
491
throw new Error("useStore must be used within StoreProvider");
492
}
493
return store;
494
}
495
496
// Components using the global store
497
function ShoppingCart() {
498
const { cart, removeFromCart } = useStore();
499
500
const total = createMemo(() =>
501
cart.reduce((sum, item) => sum + item.product.price * item.quantity, 0)
502
);
503
504
return (
505
<div>
506
<h2>Shopping Cart</h2>
507
<For each={cart} fallback={<p>Cart is empty</p>}>
508
{(item) => (
509
<div class="cart-item">
510
<span>{item.product.name} x {item.quantity}</span>
511
<span>${(item.product.price * item.quantity).toFixed(2)}</span>
512
<button onClick={() => removeFromCart(item.product.id)}>
513
Remove
514
</button>
515
</div>
516
)}
517
</For>
518
<div class="total">Total: ${total().toFixed(2)}</div>
519
</div>
520
);
521
}
522
523
function ProductList() {
524
const { addToCart } = useStore();
525
const [products] = createResource(() => fetchProducts());
526
527
return (
528
<div>
529
<h2>Products</h2>
530
<For each={products()}>
531
{(product) => (
532
<div class="product">
533
<h3>{product.name}</h3>
534
<p>${product.price}</p>
535
<button onClick={() => addToCart(product)}>
536
Add to Cart
537
</button>
538
</div>
539
)}
540
</For>
541
</div>
542
);
543
}
544
```