or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

component-system.mdcontext-scoping.mdcontrol-flow.mdindex.mdreactive-primitives.mdresources-async.mdstore-management.mdweb-rendering.md

resources-async.mddocs/

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

```