0
# Data Loading & Caching
1
2
Built-in data loading system with loaders, caching, invalidation, promise handling, and deferred data streaming for efficient data management.
3
4
## Capabilities
5
6
### Deferred Promises
7
8
Create deferred promises for streaming data loading and progressive rendering.
9
10
```typescript { .api }
11
/**
12
* Create a deferred promise for streaming data
13
* @param promise - Promise to defer
14
* @returns Deferred promise wrapper for streaming
15
*/
16
function defer<T>(promise: Promise<T>): DeferredPromise<T>;
17
18
interface DeferredPromise<T> extends Promise<T> {
19
/** Deferred promise state marker */
20
[TSR_DEFERRED_PROMISE]: DeferredPromiseState<T>;
21
/** Access to deferred state */
22
__deferredState: DeferredPromiseState<T>;
23
}
24
25
interface DeferredPromiseState<T> {
26
/** Current status of the promise */
27
status: "pending" | "success" | "error";
28
/** Resolved data if successful */
29
data?: T;
30
/** Error if promise rejected */
31
error?: any;
32
}
33
34
/** Symbol key for deferred promise state */
35
declare const TSR_DEFERRED_PROMISE: unique symbol;
36
```
37
38
**Usage Examples:**
39
40
```typescript
41
import { defer } from "@tanstack/react-router";
42
43
// In route loader - defer slow data
44
const Route = createRoute({
45
path: "/dashboard",
46
loader: async () => {
47
// Load fast data immediately
48
const user = await fetchUser();
49
50
// Defer slow data for streaming
51
const analytics = defer(fetchAnalytics());
52
const reports = defer(fetchReports());
53
54
return {
55
user, // Available immediately
56
analytics, // Streams in when ready
57
reports, // Streams in when ready
58
};
59
},
60
});
61
62
// In component - handle deferred data
63
function Dashboard() {
64
const { user, analytics, reports } = Route.useLoaderData();
65
66
return (
67
<div>
68
<h1>Welcome, {user.name}</h1>
69
70
<Suspense fallback={<div>Loading analytics...</div>}>
71
<Await promise={analytics}>
72
{(data) => <AnalyticsChart data={data} />}
73
</Await>
74
</Suspense>
75
76
<Suspense fallback={<div>Loading reports...</div>}>
77
<Await promise={reports}>
78
{(data) => <ReportsList reports={data} />}
79
</Await>
80
</Suspense>
81
</div>
82
);
83
}
84
```
85
86
### Controlled Promises
87
88
Create promises with external control for advanced async patterns.
89
90
```typescript { .api }
91
/**
92
* Create a controllable promise with external resolve/reject
93
* @returns Promise with control methods
94
*/
95
function createControlledPromise<T>(): ControlledPromise<T>;
96
97
interface ControlledPromise<T> extends Promise<T> {
98
/** Resolve the promise */
99
resolve: (value: T | PromiseLike<T>) => void;
100
/** Reject the promise */
101
reject: (reason?: any) => void;
102
/** Current status */
103
status: "pending" | "resolved" | "rejected";
104
}
105
```
106
107
**Usage Examples:**
108
109
```typescript
110
import { createControlledPromise } from "@tanstack/react-router";
111
112
// Custom async operation with external control
113
function createAsyncOperation() {
114
const { promise, resolve, reject } = createControlledPromise<string>();
115
116
// Simulate async operation
117
setTimeout(() => {
118
if (Math.random() > 0.5) {
119
resolve("Success!");
120
} else {
121
reject(new Error("Failed"));
122
}
123
}, 1000);
124
125
return {
126
promise,
127
cancel: () => reject(new Error("Cancelled")),
128
forceSuccess: () => resolve("Forced success"),
129
};
130
}
131
132
// In route loader
133
const Route = createRoute({
134
path: "/async-demo",
135
loader: () => {
136
const operation = createAsyncOperation();
137
return { result: defer(operation.promise) };
138
},
139
});
140
```
141
142
### Loader Data Hooks
143
144
Hooks for accessing loader data with type safety and selection.
145
146
```typescript { .api }
147
/**
148
* Access loader data from route with type safety
149
* @param opts - Loader data access options
150
* @returns Loader data or selected subset
151
*/
152
function useLoaderData<
153
TRouter extends AnyRouter = RegisteredRouter,
154
TFrom extends RoutePaths<TRouter> = "/",
155
TStrict extends boolean = true,
156
TSelected = ResolveLoaderData<TRouter, TFrom>,
157
TStructuralSharing extends boolean = true
158
>(
159
opts?: {
160
from?: TFrom;
161
strict?: TStrict;
162
select?: (data: ResolveLoaderData<TRouter, TFrom>) => TSelected;
163
structuralSharing?: TStructuralSharing;
164
}
165
): UseLoaderDataResult<TRouter, TFrom, TStrict, TSelected>;
166
167
/**
168
* Access loader dependencies
169
* @param opts - Loader deps access options
170
* @returns Loader dependencies
171
*/
172
function useLoaderDeps<
173
TRouter extends AnyRouter = RegisteredRouter,
174
TFrom extends RoutePaths<TRouter> = "/",
175
TStrict extends boolean = true,
176
TSelected = ResolveLoaderDeps<TRouter, TFrom>,
177
TStructuralSharing extends boolean = true
178
>(
179
opts?: {
180
from?: TFrom;
181
strict?: TStrict;
182
select?: (deps: ResolveLoaderDeps<TRouter, TFrom>) => TSelected;
183
structuralSharing?: TStructuralSharing;
184
}
185
): UseLoaderDepsResult<TRouter, TFrom, TStrict, TSelected>;
186
```
187
188
**Usage Examples:**
189
190
```typescript
191
import { useLoaderData, useLoaderDeps } from "@tanstack/react-router";
192
193
// Access loader data in component
194
function PostDetail() {
195
// Get all loader data
196
const { post, comments, relatedPosts } = useLoaderData({
197
from: "/posts/$postId",
198
});
199
200
// Select specific data
201
const postTitle = useLoaderData({
202
from: "/posts/$postId",
203
select: (data) => data.post.title,
204
});
205
206
// Access loader deps for cache invalidation
207
const deps = useLoaderDeps({
208
from: "/posts/$postId",
209
});
210
211
return (
212
<div>
213
<h1>{postTitle}</h1>
214
<div>{post.content}</div>
215
<CommentsList comments={comments} />
216
<RelatedPosts posts={relatedPosts} />
217
</div>
218
);
219
}
220
```
221
222
### Awaited Data Hook
223
224
Hook for handling deferred promises with suspense integration.
225
226
```typescript { .api }
227
/**
228
* Handle deferred promises with suspense integration
229
* @param options - Await options
230
* @returns Tuple of resolved data and promise state
231
*/
232
function useAwaited<T>(options: AwaitOptions<T>): [T, DeferredPromise<T>];
233
234
interface AwaitOptions<T> {
235
/** Promise to await */
236
promise: Promise<T> | DeferredPromise<T>;
237
}
238
```
239
240
**Usage Examples:**
241
242
```typescript
243
import { useAwaited, defer } from "@tanstack/react-router";
244
245
function StreamingDataComponent() {
246
const { deferredAnalytics } = useLoaderData();
247
248
try {
249
// This will suspend until promise resolves
250
const [analytics, promise] = useAwaited({ promise: deferredAnalytics });
251
252
return (
253
<div>
254
<h2>Analytics (Status: {promise.__deferredState.status})</h2>
255
<AnalyticsChart data={analytics} />
256
</div>
257
);
258
} catch (promise) {
259
// Suspense boundary will catch and show fallback
260
throw promise;
261
}
262
}
263
264
// Alternative usage with error handling
265
function SafeStreamingComponent() {
266
const { deferredData } = useLoaderData();
267
268
try {
269
const [data, promise] = useAwaited({ promise: deferredData });
270
271
if (promise.__deferredState.status === "error") {
272
return <div>Error loading data: {promise.__deferredState.error.message}</div>;
273
}
274
275
return <DataDisplay data={data} />;
276
} catch (promise) {
277
// Still loading
278
return <div>Loading...</div>;
279
}
280
}
281
```
282
283
### Cache Invalidation
284
285
Utilities for invalidating cached route data and triggering reloads.
286
287
```typescript { .api }
288
/**
289
* Router invalidation method
290
* Invalidates all route matches and triggers reload
291
*/
292
interface Router {
293
/**
294
* Invalidate all route data and reload
295
* @returns Promise that resolves when invalidation completes
296
*/
297
invalidate(): Promise<void>;
298
299
/**
300
* Preload a route's data
301
* @param options - Navigation options for route to preload
302
* @returns Promise that resolves when preloading completes
303
*/
304
preloadRoute<TFrom extends RoutePaths<TRouteTree> = "/">(
305
options: NavigateOptions<TRouteTree, TFrom>
306
): Promise<void>;
307
}
308
```
309
310
**Usage Examples:**
311
312
```typescript
313
import { useRouter } from "@tanstack/react-router";
314
315
function DataManagement() {
316
const router = useRouter();
317
318
const handleRefreshData = async () => {
319
// Invalidate all cached data and reload
320
await router.invalidate();
321
};
322
323
const handlePreloadProfile = async () => {
324
// Preload profile data
325
await router.preloadRoute({ to: "/profile" });
326
};
327
328
return (
329
<div>
330
<button onClick={handleRefreshData}>Refresh All Data</button>
331
<button onClick={handlePreloadProfile}>Preload Profile</button>
332
</div>
333
);
334
}
335
```
336
337
### Route Loader Functions
338
339
Types and utilities for defining route data loaders.
340
341
```typescript { .api }
342
/**
343
* Route loader function type
344
*/
345
type RouteLoaderFn<TRoute extends AnyRoute = AnyRoute> = (
346
context: LoaderFnContext<TRoute>
347
) => any | Promise<any>;
348
349
interface LoaderFnContext<TRoute extends AnyRoute = AnyRoute> {
350
/** Route parameters */
351
params: ResolveParams<TRoute>;
352
/** Search parameters */
353
search: InferFullSearchSchema<TRoute>;
354
/** Route context from beforeLoad */
355
context: RouteContext<TRoute>;
356
/** Current location */
357
location: ParsedLocation;
358
/** Abort signal for cancellation */
359
signal: AbortSignal;
360
/** Preload flag */
361
preload?: boolean;
362
}
363
364
/**
365
* Loader data resolution types
366
*/
367
type ResolveLoaderData<TRouter extends AnyRouter, TFrom extends RoutePaths<TRouter>> =
368
RouteById<TRouter, TFrom>["loaderData"];
369
370
type ResolveLoaderDeps<TRouter extends AnyRouter, TFrom extends RoutePaths<TRouter>> =
371
RouteById<TRouter, TFrom>["loaderDeps"];
372
```
373
374
**Usage Examples:**
375
376
```typescript
377
// Advanced loader with error handling and caching
378
const Route = createRoute({
379
path: "/api/data/$id",
380
loader: async ({ params, search, context, signal, preload }: LoaderFnContext) => {
381
// Check if this is a preload
382
if (preload) {
383
// Maybe return cached data for preloads
384
return getCachedData(params.id);
385
}
386
387
// Use abort signal for cleanup
388
const controller = new AbortController();
389
signal.addEventListener("abort", () => controller.abort());
390
391
try {
392
// Load multiple data sources
393
const [item, metadata] = await Promise.all([
394
fetchItem(params.id, { signal: controller.signal }),
395
fetchMetadata(params.id, { signal: controller.signal }),
396
]);
397
398
// Apply search filters
399
const filteredData = applySearchFilters(item, search);
400
401
// Use context for authorization
402
const authorizedData = await authorizeData(filteredData, context.user);
403
404
return {
405
item: authorizedData,
406
metadata,
407
loadedAt: Date.now(),
408
};
409
} catch (error) {
410
if (error.name === "AbortError") {
411
throw new Error("Request cancelled");
412
}
413
throw error;
414
}
415
},
416
// Configure caching
417
staleTime: 5 * 60 * 1000, // 5 minutes
418
gcTime: 30 * 60 * 1000, // 30 minutes
419
});
420
```
421
422
### Data Transformation
423
424
Utilities for transforming and updating loader data.
425
426
```typescript { .api }
427
/**
428
* Apply functional updates to data
429
* @param updater - Update function or new value
430
* @param previous - Previous value
431
* @returns Updated value
432
*/
433
function functionalUpdate<T>(
434
updater: T | ((prev: T) => T),
435
previous: T
436
): T;
437
438
/**
439
* Deep equality replacement for structural sharing
440
* @param prev - Previous value
441
* @param next - Next value
442
* @returns Previous if equal, next if different
443
*/
444
function replaceEqualDeep<T>(prev: T, next: T): T;
445
```
446
447
**Usage Examples:**
448
449
```typescript
450
import { functionalUpdate, replaceEqualDeep } from "@tanstack/react-router";
451
452
// Functional updates in loader
453
const Route = createRoute({
454
path: "/settings",
455
loader: async ({ context }) => {
456
const settings = await fetchSettings();
457
458
// Apply functional update based on context
459
const updatedSettings = functionalUpdate(
460
(prev) => ({
461
...prev,
462
theme: context.user.preferredTheme,
463
}),
464
settings
465
);
466
467
return { settings: updatedSettings };
468
},
469
});
470
471
// Structural sharing for performance
472
function useOptimizedData() {
473
const data = useLoaderData();
474
475
// Only re-render if data actually changed
476
const optimizedData = useMemo(() =>
477
replaceEqualDeep(previousData.current, data),
478
[data]
479
);
480
481
return optimizedData;
482
}
483
```
484
485
## Types
486
487
### Loader Types
488
489
```typescript { .api }
490
interface LoaderContext<TRoute extends AnyRoute = AnyRoute> {
491
params: ResolveParams<TRoute>;
492
search: InferFullSearchSchema<TRoute>;
493
context: RouteContext<TRoute>;
494
location: ParsedLocation;
495
signal: AbortSignal;
496
preload?: boolean;
497
}
498
499
type LoaderData<TRoute extends AnyRoute = AnyRoute> = TRoute extends {
500
loader: infer TLoader;
501
}
502
? TLoader extends (...args: any[]) => infer TReturn
503
? TReturn extends Promise<infer TData>
504
? TData
505
: TReturn
506
: never
507
: {};
508
```
509
510
### Promise State Types
511
512
```typescript { .api }
513
interface DeferredPromiseState<T> {
514
status: "pending" | "success" | "error";
515
data?: T;
516
error?: any;
517
}
518
519
type ControllablePromise<T> = Promise<T> & {
520
resolve: (value: T | PromiseLike<T>) => void;
521
reject: (reason?: any) => void;
522
status: "pending" | "resolved" | "rejected";
523
};
524
```
525
526
### Cache Configuration Types
527
528
```typescript { .api }
529
interface CacheOptions {
530
/** Time until data becomes stale */
531
staleTime?: number;
532
/** Time until data is garbage collected */
533
gcTime?: number;
534
/** Whether route should reload on focus */
535
shouldReload?: boolean | ((match: RouteMatch) => boolean);
536
}
537
```