Use cases
Three patterns where SWR caching on resource() earns its keep.
Admin dashboard with tabs
Section titled “Admin dashboard with tabs”Pain: The user switches between Orders, Invoices, and Customers. Every tab shows a spinner even when its data has not changed.
Fix: Tabs read from a shared cache. The first visit fetches; every return is immediate. Background refresh starts only when data is stale.
// Each tab is a route. Each route's store reads from a shared cache.@Injectable() export class OrdersStore { readonly orders = cachedResource({ cache: this.api.cache, cacheKey: ['orders', 'list'], loader: () => this.api.getOrders$(), })}
// Tab switch → store destroyed → cache survives in the API service.// Switch back → cachedResource reads cache → instant. Background refetch// kicks off if the entry is stale.E-commerce list → detail → back
Section titled “E-commerce list → detail → back”Pain: The user filters a list, opens a product, edits something, then navigates back. Filter state survives, but the list reloads from scratch.
Fix: Use hierarchical keys. One invalidate(['product']) after the mutation
refreshes both views.
// List + detail share a key prefix. One mutation invalidates both.cacheKey: params => ['product', 'list', params.status, params.sortBy]cacheKey: params => ['product', 'details', params.id]
// After a mutation:cache.invalidate(['product']) // marks BOTH list and details stale// Background refetch on the visible view, untouched cache for the rest.Multi-step form with dependent data
Section titled “Multi-step form with dependent data”Pain: Step 2 depends on Step 1. The user goes back, changes Step 1, then returns to Step 2. The dependent fetch runs every time.
Fix: params returns undefined until ready. Once selected, the fetch runs
once and caches per key. Re-selecting an earlier value is immediate.
// Step 2's params depend on Step 1's selection.readonly categoryId = signal<string | null>(null)
readonly products = cachedResource({ cache: this.api.cache, cacheKey: p => ['product', 'by-category', p.categoryId], params: () => { const id = this.categoryId() return id ? { categoryId: id } : undefined // idle until set }, loader: ({ params }) => this.api.getByCategory$(params.categoryId),})
// User backs to Step 1, picks a different category → resource refetches.// Re-picks the original category → instant from cache.