0
# Resources and Async
1
2
Resource system for handling asynchronous data loading with built-in loading states, error handling, and automatic refetching capabilities.
3
4
## Capabilities
5
6
### Creating Resources
7
8
Create reactive resources that wrap promises and provide loading states.
9
10
```typescript { .api }
11
/**
12
* Creates a resource without a source signal
13
* @param fetcher - Function that returns data or a promise
14
* @param options - Configuration options for the resource
15
* @returns Resource tuple with data accessor and actions
16
*/
17
function createResource<T, R = unknown>(
18
fetcher: ResourceFetcher<true, T, R>,
19
options?: ResourceOptions<T, true>
20
): ResourceReturn<T, R>;
21
22
/**
23
* Creates a resource with a source signal that triggers refetching
24
* @param source - Source signal that triggers refetching when it changes
25
* @param fetcher - Function that fetches data based on source value
26
* @param options - Configuration options for the resource
27
* @returns Resource tuple with data accessor and actions
28
*/
29
function createResource<T, S, R = unknown>(
30
source: ResourceSource<S>,
31
fetcher: ResourceFetcher<S, T, R>,
32
options?: ResourceOptions<T, S>
33
): ResourceReturn<T, R>;
34
35
type ResourceSource<S> = S | false | null | undefined | (() => S | false | null | undefined);
36
type ResourceFetcher<S, T, R = unknown> = (
37
source: S,
38
info: ResourceFetcherInfo<T, R>
39
) => T | Promise<T>;
40
41
interface ResourceFetcherInfo<T, R = unknown> {
42
value: T | undefined;
43
refetching: R | boolean;
44
}
45
46
interface ResourceOptions<T, S = unknown> {
47
initialValue?: T;
48
name?: string;
49
deferStream?: boolean;
50
ssrLoadFrom?: "initial" | "server";
51
storage?: (init: T | undefined) => [Accessor<T | undefined>, Setter<T | undefined>];
52
onHydrated?: (k: S | undefined, info: { value: T | undefined }) => void;
53
}
54
```
55
56
**Usage Examples:**
57
58
```typescript
59
import { createResource, createSignal, Show, Suspense } from "solid-js";
60
61
// Simple resource without source
62
const [data] = createResource(async () => {
63
const response = await fetch("/api/users");
64
return response.json();
65
});
66
67
// Resource with source signal
68
function UserProfile() {
69
const [userId, setUserId] = createSignal<number | null>(null);
70
71
const [user] = createResource(userId, async (id) => {
72
if (!id) return null;
73
const response = await fetch(`/api/users/${id}`);
74
if (!response.ok) throw new Error("User not found");
75
return response.json();
76
});
77
78
return (
79
<div>
80
<input
81
type="number"
82
placeholder="Enter user ID"
83
onInput={(e) => setUserId(parseInt(e.target.value) || null)}
84
/>
85
86
<Show when={userId()}>
87
<Suspense fallback={<div>Loading user...</div>}>
88
<Show when={user()} fallback={<div>User not found</div>}>
89
{(userData) => (
90
<div>
91
<h2>{userData.name}</h2>
92
<p>Email: {userData.email}</p>
93
</div>
94
)}
95
</Show>
96
</Suspense>
97
</Show>
98
</div>
99
);
100
}
101
```
102
103
### Resource States and Actions
104
105
Resources provide various states and actions for managing async data.
106
107
```typescript { .api }
108
type ResourceReturn<T, R = unknown> = [
109
resource: Resource<T>,
110
actions: {
111
mutate: Setter<T | undefined>;
112
refetch: (info?: R) => T | Promise<T> | undefined | null;
113
}
114
];
115
116
type Resource<T> = Accessor<T | undefined> & {
117
state: "unresolved" | "pending" | "ready" | "refreshing" | "errored";
118
loading: boolean;
119
error: any;
120
latest: T | undefined;
121
};
122
```
123
124
**Usage Examples:**
125
126
```typescript
127
import { createResource, createSignal, Show, ErrorBoundary } from "solid-js";
128
129
function DataManager() {
130
const [refreshTrigger, setRefreshTrigger] = createSignal(0);
131
132
const [data, { mutate, refetch }] = createResource(
133
refreshTrigger,
134
async () => {
135
console.log("Fetching data...");
136
const response = await fetch("/api/data");
137
if (!response.ok) throw new Error("Failed to fetch");
138
return response.json();
139
},
140
{ initialValue: [] }
141
);
142
143
return (
144
<div>
145
<div class="toolbar">
146
<button
147
onClick={() => refetch()}
148
disabled={data.loading}
149
>
150
{data.loading ? "Refetching..." : "Refetch"}
151
</button>
152
153
<button
154
onClick={() => setRefreshTrigger(prev => prev + 1)}
155
>
156
Trigger Refresh
157
</button>
158
159
<button
160
onClick={() => mutate(prev => [...(prev || []), { id: Date.now(), name: "New Item" }])}
161
>
162
Add Item (Optimistic)
163
</button>
164
</div>
165
166
<div class="status">
167
<p>State: {data.state}</p>
168
<p>Loading: {data.loading ? "Yes" : "No"}</p>
169
<Show when={data.error}>
170
<p class="error">Error: {data.error.message}</p>
171
</Show>
172
</div>
173
174
<ErrorBoundary fallback={(err) => <div>Error: {err.message}</div>}>
175
<Show when={data()} fallback={<div>No data</div>}>
176
{(items) => (
177
<ul>
178
<For each={items}>
179
{(item) => <li>{item.name}</li>}
180
</For>
181
</ul>
182
)}
183
</Show>
184
</ErrorBoundary>
185
</div>
186
);
187
}
188
```
189
190
### Suspense Integration
191
192
Resources integrate seamlessly with Suspense for declarative loading states.
193
194
```typescript { .api }
195
/**
196
* Tracks all resources inside a component and renders a fallback until they are all resolved
197
* @param props - Suspense component props
198
* @returns JSX element with loading state management
199
*/
200
function Suspense(props: {
201
fallback?: JSX.Element;
202
children: JSX.Element;
203
}): JSX.Element;
204
```
205
206
**Usage Examples:**
207
208
```typescript
209
import { createResource, Suspense, ErrorBoundary, For } from "solid-js";
210
211
function App() {
212
const [users] = createResource(() => fetchUsers());
213
const [posts] = createResource(() => fetchPosts());
214
215
return (
216
<div>
217
<h1>My App</h1>
218
219
<ErrorBoundary fallback={(err) => <div>Error: {err.message}</div>}>
220
<Suspense fallback={<div>Loading application data...</div>}>
221
<div class="content">
222
<section>
223
<h2>Users</h2>
224
<For each={users()}>
225
{(user) => <div class="user">{user.name}</div>}
226
</For>
227
</section>
228
229
<section>
230
<h2>Posts</h2>
231
<For each={posts()}>
232
{(post) => <div class="post">{post.title}</div>}
233
</For>
234
</section>
235
</div>
236
</Suspense>
237
</ErrorBoundary>
238
</div>
239
);
240
}
241
242
async function fetchUsers() {
243
await new Promise(resolve => setTimeout(resolve, 1000));
244
return [{ id: 1, name: "John" }, { id: 2, name: "Jane" }];
245
}
246
247
async function fetchPosts() {
248
await new Promise(resolve => setTimeout(resolve, 1500));
249
return [{ id: 1, title: "Post 1" }, { id: 2, title: "Post 2" }];
250
}
251
```
252
253
### Advanced Resource Patterns
254
255
Handle complex async scenarios with advanced resource patterns.
256
257
**Usage Examples:**
258
259
```typescript
260
import { createResource, createSignal, createMemo, batch } from "solid-js";
261
262
// Dependent resources
263
function UserDashboard() {
264
const [userId, setUserId] = createSignal(1);
265
266
const [user] = createResource(userId, async (id) => {
267
const response = await fetch(`/api/users/${id}`);
268
return response.json();
269
});
270
271
// Posts depend on user data
272
const [posts] = createResource(
273
() => user()?.id,
274
async (userId) => {
275
if (!userId) return [];
276
const response = await fetch(`/api/users/${userId}/posts`);
277
return response.json();
278
}
279
);
280
281
return (
282
<Suspense fallback={<div>Loading dashboard...</div>}>
283
<div>
284
<Show when={user()}>
285
{(userData) => (
286
<div>
287
<h1>{userData.name}'s Dashboard</h1>
288
<Suspense fallback={<div>Loading posts...</div>}>
289
<For each={posts()}>
290
{(post) => <div class="post">{post.title}</div>}
291
</For>
292
</Suspense>
293
</div>
294
)}
295
</Show>
296
</div>
297
</Suspense>
298
);
299
}
300
301
// Resource with caching
302
function CachedDataExample() {
303
const cache = new Map();
304
305
const [cacheKey, setCacheKey] = createSignal("default");
306
307
const [data] = createResource(
308
cacheKey,
309
async (key) => {
310
if (cache.has(key)) {
311
console.log("Cache hit for", key);
312
return cache.get(key);
313
}
314
315
console.log("Fetching", key);
316
const response = await fetch(`/api/data/${key}`);
317
const result = await response.json();
318
cache.set(key, result);
319
return result;
320
}
321
);
322
323
return (
324
<div>
325
<select onChange={(e) => setCacheKey(e.target.value)}>
326
<option value="default">Default</option>
327
<option value="users">Users</option>
328
<option value="posts">Posts</option>
329
</select>
330
331
<Suspense fallback={<div>Loading {cacheKey()}...</div>}>
332
<pre>{JSON.stringify(data(), null, 2)}</pre>
333
</Suspense>
334
</div>
335
);
336
}
337
338
// Resource with retry logic
339
function RetryableResource() {
340
const [retryCount, setRetryCount] = createSignal(0);
341
342
const [data, { refetch }] = createResource(
343
retryCount,
344
async (count) => {
345
console.log(`Attempt ${count + 1}`);
346
347
// Simulate random failures
348
if (Math.random() < 0.7) {
349
throw new Error(`Failed on attempt ${count + 1}`);
350
}
351
352
return { message: `Success on attempt ${count + 1}!` };
353
}
354
);
355
356
const retry = () => {
357
setRetryCount(prev => prev + 1);
358
};
359
360
return (
361
<div>
362
<ErrorBoundary
363
fallback={(err, reset) => (
364
<div class="error">
365
<p>Error: {err.message}</p>
366
<button onClick={() => { reset(); retry(); }}>
367
Retry (Attempt {retryCount() + 2})
368
</button>
369
</div>
370
)}
371
>
372
<Suspense fallback={<div>Loading (Attempt {retryCount() + 1})...</div>}>
373
<Show when={data()}>
374
{(result) => (
375
<div class="success">
376
<p>{result.message}</p>
377
<button onClick={retry}>Try Again</button>
378
</div>
379
)}
380
</Show>
381
</Suspense>
382
</ErrorBoundary>
383
</div>
384
);
385
}
386
```
387
388
### Server-Side Rendering
389
390
Resources support server-side rendering with hydration capabilities.
391
392
```typescript
393
// Resource with SSR support
394
const [data] = createResource(
395
() => fetchData(),
396
{
397
ssrLoadFrom: "server", // Load from server on SSR
398
deferStream: true, // Defer streaming until resolved
399
onHydrated: (key, info) => {
400
console.log("Resource hydrated", key, info);
401
}
402
}
403
);
404
405
// Custom storage for persistence
406
const [persistedData] = createResource(
407
() => fetchData(),
408
{
409
storage: (init) => {
410
// Custom storage implementation
411
const [value, setValue] = createSignal(init);
412
413
// Save to localStorage when value changes
414
createEffect(() => {
415
const current = value();
416
if (current !== undefined) {
417
localStorage.setItem("cached-data", JSON.stringify(current));
418
}
419
});
420
421
// Load from localStorage on mount
422
onMount(() => {
423
const stored = localStorage.getItem("cached-data");
424
if (stored) {
425
setValue(JSON.parse(stored));
426
}
427
});
428
429
return [value, setValue];
430
}
431
}
432
);
433
```
434
435
## Types
436
437
### Resource Types
438
439
```typescript { .api }
440
type Resource<T> = Accessor<T | undefined> & {
441
state: "unresolved" | "pending" | "ready" | "refreshing" | "errored";
442
loading: boolean;
443
error: any;
444
latest: T | undefined;
445
};
446
447
type ResourceActions<T, R = unknown> = {
448
mutate: Setter<T>;
449
refetch: (info?: R) => T | Promise<T> | undefined | null;
450
};
451
452
type ResourceReturn<T, R = unknown> = [Resource<T>, ResourceActions<T | undefined, R>];
453
454
type InitializedResource<T> = Accessor<T> & {
455
state: "ready" | "refreshing" | "errored";
456
loading: boolean;
457
error: any;
458
latest: T;
459
};
460
461
type InitializedResourceReturn<T, R = unknown> = [
462
resource: InitializedResource<T>,
463
actions: {
464
mutate: Setter<T>;
465
refetch: (info?: R) => T | Promise<T> | undefined | null;
466
}
467
];
468
469
interface InitializedResourceOptions<T, S = unknown> extends ResourceOptions<T, S> {
470
initialValue: T;
471
}
472
```
473
474
### Fetcher Types
475
476
```typescript { .api }
477
type ResourceFetcher<S, T> = (
478
source: S,
479
info: ResourceFetcherInfo<T>
480
) => T | Promise<T>;
481
482
interface ResourceFetcherInfo<T> {
483
value: T | undefined;
484
refetching: boolean | unknown;
485
}
486
487
type ResourceSource<S> = S | false | null | (() => S | false | null);
488
```