Guides
Caching Examples
Caching Examples
This guide illustrates the caching lifecycle in SwrSharp, including cache hits, background refetching, and stale data handling.
Basic Example
Let's assume we are using the default staleTime of 0 (data is immediately stale).
A new
UseQueryfor["todos"]mounts.- Since no other queries have been made with the
["todos"]key, this query will show a loading state and make a network request to fetch the data. - When the network request completes, the returned data is cached under the
["todos"]key. - The data is immediately marked as stale (default
staleTimeis0).
- Since no other queries have been made with the
A second
UseQueryfor["todos"]mounts elsewhere.- The cache already has data for
["todos"], so that data is immediately returned from the cache. - A background refetch is triggered because the data is stale.
- When the background refetch completes, both query instances are updated with the fresh data.
- The cache already has data for
Both query instances unmount.
- The cached data remains in the
QueryClientcache and can be reused by future queries.
- The cached data remains in the
A new
UseQueryfor["todos"]mounts later.- The cached data is immediately available and returned.
- A background refetch runs to ensure freshness.
// First query - triggers network request
var query1 = new UseQuery<Todo[]>(new QueryOptions<Todo[]>(
queryKey: new QueryKey("todos"),
queryFn: async ctx => await FetchTodos(ctx.Signal)
), queryClient);
// Second query (same key) - gets cached data instantly, refetches in background
var query2 = new UseQuery<Todo[]>(new QueryOptions<Todo[]>(
queryKey: new QueryKey("todos"),
queryFn: async ctx => await FetchTodos(ctx.Signal)
), queryClient);
Stale Time Example
With a non-zero staleTime, queries won't trigger background refetches until the data becomes stale:
var query = new UseQuery<Todo[]>(new QueryOptions<Todo[]>(
queryKey: new QueryKey("todos"),
queryFn: async ctx => await FetchTodos(ctx.Signal)
)
{
StaleTime = TimeSpan.FromMinutes(5) // Data is fresh for 5 minutes
}, queryClient);
Timeline:
t=0: Query fetches data, caches it, data is fresht=2min: Another component mounts with same key — gets cached data, no refetch (still fresh)t=5min: Data becomes stalet=6min: Another component mounts — gets cached data, triggers background refetch (stale)
Cache Sharing
All queries with the same key share the same cache entry. When any one of them refetches, all instances receive the updated data:
// Component A
var queryA = new UseQuery<User>(new QueryOptions<User>(
queryKey: new QueryKey("user", 1),
queryFn: async ctx => await FetchUser(1, ctx.Signal)
), queryClient);
// Component B (different component, same query key)
var queryB = new UseQuery<User>(new QueryOptions<User>(
queryKey: new QueryKey("user", 1),
queryFn: async ctx => await FetchUser(1, ctx.Signal)
), queryClient);
// When queryA refetches, queryB also receives the new data
// When queryB refetches, queryA also receives the new data
Cache Invalidation Lifecycle
When a query is invalidated, here's what happens:
- The cache entry is removed
- If the query is currently being observed (active), it automatically refetches
- The query transitions to a loading/fetching state during the refetch
// Invalidate a specific query
queryClient.InvalidateQueries(new QueryFilters
{
QueryKey = new QueryKey("todos")
});
// Invalidate after a mutation
var mutation = new UseMutation<Todo, CreateTodoInput>(
new MutationOptions<Todo, CreateTodoInput>
{
MutationFn = async input => await CreateTodo(input),
OnSuccess = async (data, variables, onMutateResult, context) =>
{
// Invalidate to refetch with the new todo included
context.Client.InvalidateQueries(new QueryFilters
{
QueryKey = new QueryKey("todos")
});
}
},
queryClient
);
Prefetching and the Cache
You can populate the cache before a query is needed using PrefetchQueryAsync:
// Prefetch data (e.g., on hover or route change)
await queryClient.PrefetchQueryAsync(new QueryOptions<Todo[]>(
queryKey: new QueryKey("todos"),
queryFn: async ctx => await FetchTodos(ctx.Signal)
));
// Later, when the query mounts, data is instantly available from cache
var query = new UseQuery<Todo[]>(new QueryOptions<Todo[]>(
queryKey: new QueryKey("todos"),
queryFn: async ctx => await FetchTodos(ctx.Signal)
), queryClient);
// query.Data is immediately available!
Manual Cache Updates
You can directly read and write cache entries without triggering refetches:
// Read from cache
var todos = queryClient.GetQueryData<List<Todo>>(new QueryKey("todos"));
// Write to cache (e.g., after a mutation returns updated data)
queryClient.SetQueryData(new QueryKey("todo", 5), updatedTodo);
// This is useful for optimistic updates and avoiding redundant network calls
Cache Cleanup
SwrSharp does not currently implement automatic garbage collection. Cache entries persist until explicitly removed:
// Remove a specific cache entry
queryClient.Invalidate(new QueryKey("todos"));
// Clear all cache entries (typically on logout or app shutdown)
queryClient.Dispose();
Note: Automatic garbage collection with configurable
gcTimeis planned for a future release.
Key Takeaways
| Concept | Behavior |
|---|---|
| Cache hit | Queries with the same key share cached data instantly |
| Stale data | Stale queries trigger background refetches on mount, window focus, or reconnect |
| Fresh data | Fresh queries (within staleTime) do not refetch |
| Invalidation | Removes cache and triggers refetch for active queries |
| Prefetching | Populates cache before a query mounts |
| Manual updates | SetQueryData updates cache without network requests |
| Cache lifetime | Entries persist until invalidated or QueryClient is disposed |