Remote / foldkit-remote
RemoteData: what does the Model know right now?
A Remote Projection never lies by pretending an absent value is present. It
returns a closed RemoteData union:
no active fetch
missing ------------------------------------------------> Initial
fetch starts
missing ------------------------------------------------> Loading
|
result
v
Ready
|
refetch starts
v
Refreshing
old value stays
|
result
v
Ready
server says entity is absent ---------------------------> NotFound
stored value fails Selection decoding -----------------> Failed
a read or a list's query fails ------------------------> Failed (with the old value, if any)The states are:
Initial— required data is absent and nothing is currently fetching it.Loading— required data is absent and a read, or a list's query, is in flight.Ready— every selected field is present and decodes.Refreshing— the current value remains visible while it is being refetched.Failed— stored server data does not decode against the Selection, the read or query behind it failed, or the server settled a selected field without a value (anUnavailableerror; see When a field is withheld). A value that was already shown is kept asprevious.NotFound— the entity is represented by a tombstone: a live event deleted it, or the server was asked for it by id and answered with nothing about it. That id is then known absent, whether it never existed, is gone, or is hidden whole from this principal. It is not planned again untilData.refreshforces it or a write brings it back. The target of a returned ref is not marked this way: a server need not expand a relation that rides on a request, and the planner asks for such a target by id next, which is when its absence is learned.
Initial is intentionally different from Loading. A Projection belonging to
no active Surface may remain Initial forever. Rendering a spinner for
Initial can therefore hide an activation/wiring mistake; Loading is the
state that actually means "wait for this request."
When a read sits at Initial and you expected data, ask the Model why with
Data.why; see Why is it still Initial?.
RemoteData.match is exhaustive, so adding or omitting a state is visible at
compile time. RemoteData.answered(data) is whether a read has an answer yet:
Ready, Refreshing, Failed or NotFound, not Initial or Loading.
Drawing one: RemoteData.render
Most views draw these six states three ways, under one policy: useful data
stays on screen. RemoteData.render is that fold.
RemoteData.render(project, {
loading: () => ProjectSkeleton(),
notFound: () => NoSuchProject(),
failed: error => ErrorView(error),
data: (value, freshness) =>
ProjectView({ project: value, dimmed: freshness._tag !== 'Fresh' }),
})Initial and Loading reach loading; Ready and Refreshing reach data;
a Failed that still carries the value it had reaches data too, so a read
that failed does not throw away what the reader was already looking at. Only a
Failed with nothing to show reaches failed.
The data branch is told which it got, as a Freshness — exported, so a view
helper that takes one can name it:
Freshness | What it means |
|---|---|
Fresh | This is the current answer. |
Refreshing | A newer answer is on its way; this one is still good. |
Stale | A read failed; this is what was last known good, with its error. |
It is one tag rather than a pair of booleans because a value cannot be both refreshing and stale, and a type that can say so invites a view to handle a state that never arrives.
notFound is its own branch and not optional. A row the server answered for
and does not have is neither loading nor a failure; drawing it as either is a
spinner that never ends or an error nobody can act on.
Reach for match instead when the six states really do draw differently — it
stays the exhaustive fold, and render does not replace it.