Refetching & staleness

Stale time decides when cached data needs revalidating; Refetch() is the explicit override that always fetches.

Stale time#

Data is fresh for StaleTime after a successful fetch. A keyed Execute that finds fresh data serves the cache and does not fetch. The library default is TimeSpan.Zero — cached data renders instantly but is always revalidated in the background on the next mount. "Cached" does not mean "no request" unless you raise the stale time (this site uses 10 seconds).

RefetchOnMount#

Controls whether a keyed Execute that finds cached data also refetches:

Value TypeDescription
IfStale defaultRefetch in the background only when the data is older than the stale time.
Always Always refetch in the background, even while fresh.
Never Serve the cache and never refetch on mount. Invalidation still refetches.

A key with no cached data always fetches, regardless of this setting — and an invalidated entry refetches even under Never: invalidation is an explicit request, not a mount policy.

Manual Refetch()#

Refetch() re-runs the last Execute with a forced fresh fetch — staleness, RefetchOnMount and Enabled are all bypassed. On the cached path the result updates the shared entry, so every component on the key re-renders; a concurrent same-key fetch is joined rather than duplicated. Before the first Execute (or after disposal) it does nothing.

<button @onclick="() => _todos.Refetch()">Refresh</button>

// with one-shot overrides:
await _todos.Refetch(new RefetchParameters<List<Todo>>
{
    StaleTime = TimeSpan.FromSeconds(30),   // how soon the fresh result goes stale again
    OnSettled = _ => _lastRefreshed = DateTimeOffset.Now,
});

One-shot overrides#

RefetchParameters<T> can override callbacks, the token, and the stale/cache times — for that call only. The key and fetch function always come from the last Execute: changing those is a new query, not a refetch. A later bare Refetch() is unaffected by earlier overrides.

Precedence of defaults#

Wherever stale time, cache time or RefetchOnMount can be set, the chain is:

  1. the per-execution value on QueryParameters<T>,
  2. else the longest matching prefix registered via DejaClient.SetDefaults,
  3. else the global DejaOptions default.
// Per-prefix defaults: reference data rarely changes, keep it fresh for an hour.
client.SetDefaults(QueryKey.Of("countries"), new QueryDefaults
{
    StaleTime = TimeSpan.FromHours(1),
    RefetchOnMount = RefetchOnMount.Never,
});

Live demo#

Refetch and IsReFetching
List query IsLoading IsReFetching IsError IsCachedData IsStale ReFetchCount: 1UpdatedAt: 20:32:43
  • delectus aut autem
  • quis ut nam facilis et officia qui
  • fugiat veniam minus

A manual Refetch() always fetches — staleness, RefetchOnMount and Enabled are bypassed. The existing list stays on screen while IsReFetching is on: no loading flash. The override variant keeps the fresh result “not stale” for 30 seconds instead of the site default of 10 — for this call only.

@inherits DejaComponentBase
@inject JsonPlaceholderApi Api

<div class="demo-toolbar">
    <button class="demo-button primary" @onclick="() => _todos.Refetch()">Refetch()</button>
    <button class="demo-button" @onclick="RefetchWithOverride">Refetch with StaleTime = 30 s</button>
</div>

<StateInspector Query="_todos" Label="List query"/>

@if (_todos.Data is { } todos)
{
    <ul class="demo-list">
        @foreach (var todo in todos)
        {
            <li>@todo.Title</li>
        }
    </ul>
}
else if (_todos.IsLoading)
{
    <p class="demo-note">Loading…</p>
}

@if (_lastRefetchAt is { } at)
{
    <p class="demo-note">Last manual refetch settled at @at.ToString("HH:mm:ss").</p>
}

<p class="demo-note">
    A manual <code>Refetch()</code> always fetches — staleness, <code>RefetchOnMount</code> and
    <code>Enabled</code> are bypassed. The existing list stays on screen while
    <code>IsReFetching</code> is on: no loading flash. The override variant keeps the fresh result
    “not stale” for 30 seconds instead of the site default of 10 — for this call only.
</p>

@code {
    private readonly Query<IReadOnlyList<TodoDto>> _todos = new();
    private DateTimeOffset? _lastRefetchAt;

    protected override Task OnInitializedAsync()
        => _todos.Execute(DocsKeys.TodoList(3), token => Api.GetTodosAsync(3, token), p => p.OnError = _ => { });

    private Task RefetchWithOverride() => _todos.Refetch(new RefetchParameters<IReadOnlyList<TodoDto>>
    {
        StaleTime = TimeSpan.FromSeconds(30),
        OnSettled = _ => _lastRefetchAt = DateTimeOffset.Now,
    });
}