RemoteData — Loading States
1.87 kB minified + gzipped
Representing asynchronous UI state with independent boolean flags (data, isLoading, error)
allows impossible state combinations (such as isLoading: true while an error is displayed) and
makes initial un-fetched states ambiguous.
RemoteData<E, A> models the complete request lifecycle as a discriminated union of four mutually
exclusive states: NotAsked, Loading, Failure<E>, and Success<A>:
stateDiagram-v2 direction TB [*] --> NotAsked NotAsked --> Loading : request starts Loading --> Success : data arrives (Success A) Loading --> Failure : network fails (Failure E) Failure --> Loading : retry Success --> Loading : refresh
By packaging our loading state into a single, cohesive type, we ensure that our application can only ever represent valid states.
Creating RemoteData
Section titled “Creating RemoteData”To track a lifecycle, we transition our state through the explicit constructors of RemoteData:
Inspecting states with type guards: is.*
Section titled “Inspecting states with type guards: is.*”When checking a specific lifecycle state in imperative code or UI conditionals, use the
RemoteData.is type guards (notAsked, loading, failure, success):
Exhaustive Rendering with match
Section titled “Exhaustive Rendering with match”The primary benefit of RemoteData is that it makes your rendering and consumption logic safe. When
you unpack the data, match requires you to handle all four branches explicitly. If you forget one,
the compiler will refuse to build your application:
Because the compiler enforces that every branch is defined, you can never accidentally skip the loading spinner or forget to handle a network failure.
Transforming States in Flight
Section titled “Transforming States in Flight”We do not have to immediately unpack RemoteData to work with it. We can describe transformations
on our successful values while they are still managed within their lifecycle context.
Pure transformations with map
Section titled “Pure transformations with map”map applies a transformation to the value inside a Success container, leaving NotAsked,
Loading, and Failure entirely untouched:
If the state is Loading, it remains Loading after the map. The transformation is only executed
if the data has successfully arrived, allowing us to describe what should happen to the value
without worrying about whether it is loading or failed.
Standardizing errors with mapError
Section titled “Standardizing errors with mapError”mapError applies a transformation to the error inside a Failure container, leaving all other
states untouched. This is useful for translating low-level API error objects into user-friendly
message strings:
Combining multiple states with apply
Section titled “Combining multiple states with apply”When a UI view depends on multiple independent RemoteData resources (such as a user profile and
their billing plan), RemoteData.apply merges their lifecycles:
- If all inputs are
Success, the curried function is applied. - If any input is
Failure, the firstFailureis propagated. - Otherwise, if any input is
Loading, the combined state isLoading. - Otherwise, the combined state is
NotAsked.
Chaining Dependent Requests
Section titled “Chaining Dependent Requests”Often, one asynchronous state depends on the result of another — for instance, looking up a selected organization’s details from a loaded user record.
For this, we use chain. If the current state is Success, chain passes the value to your next
function and returns its RemoteData state. If the state is Loading, Failure, or NotAsked,
the step is skipped and that state propagates automatically:
Validating outcomes: filter
Section titled “Validating outcomes: filter”Sometimes a fetch succeeds, but the data does not satisfy our business constraints (e.g., a
retrieved price list is empty). We can use filter to convert a Success into a Failure based on
a condition:
If the fetch succeeded with -1, the state is converted to Failure("Price of -1 is invalid...").
If the state was Loading, the check is bypassed.
Extracting and Recovering
Section titled “Extracting and Recovering”Safe fallbacks with getOrElse
Section titled “Safe fallbacks with getOrElse”When you simply need to extract the success value or supply a fallback if the data is loading,
absent, or failed, you can use getOrElse:
Recovering from failures with recover
Section titled “Recovering from failures with recover”When a RemoteData state is Failure and you want to fall back to a cached RemoteData state (or
recover to a default Success value), use RemoteData.recover:
Side effects with tap and tapError
Section titled “Side effects with tap and tapError”To run a side effect when data has arrived (Success) or when an error has occurred (Failure)
without altering the RemoteData state, use tap and tapError:
Interoperability at boundaries
Section titled “Interoperability at boundaries”RemoteData works naturally in UI states, while lower-level utilities usually communicate using
Maybe or Result types. You can convert between them at system boundaries.
Downgrading to Maybe or Result
Section titled “Downgrading to Maybe or Result”To discard all error and loading information, you can convert to Maybe using to.Maybe. Only
Success becomes a Some:
To convert to a Result, you must provide a fallback error to represent what should happen if the
data is still NotAsked or Loading:
Converting from Result and Maybe
Section titled “Converting from Result and Maybe”When you trigger an asynchronous operation (e.g. using a Task.Result), you obtain a standard
Result<E, A> when it resolves. Use RemoteData.from.Result to convert that Result directly into
Success or Failure:
Similarly, if you have an optional Maybe<A> from a local cache lookup, use RemoteData.from.Maybe
to convert Some into Success and None into Failure:
Pipeline composition
Section titled “Pipeline composition”Here is a complete UI state pipeline that takes a raw RemoteData response for an invoice, filters
out invalid totals, recovers from transient offline errors using a cached snapshot, and renders a
status string for the view:
Problems it solves
Section titled “Problems it solves”- Eliminating impossible UI states: Managing asynchronous data in UI components with separate
variables (
isLoading,isError,data,errorMessage) creates impossible combinations, like displaying a loading spinner alongside an error banner and stale data.RemoteDataunifies these into a single four-state discriminated union (NotAsked,Loading,Failure,Success), guaranteeing impossible states cannot be represented. - Exhaustive view branch rendering: Interactive components in frontend frameworks require
distinct presentations for idle states, loading skeletons, error recovery cards, and populated
content.
RemoteData.matchenforces exhaustive handling of each lifecycle stage at compile time, eliminating blank screens caused by unhandled loading or error conditions. - Coordinating multi-resource dashboard widgets (
RemoteData.apply): When a dashboard widget depends on multiple asynchronous queries (such as user permissions and account metrics),RemoteData.applymerges their lifecycles cleanly, ensuring data views render only when every dependency succeeds while propagatingLoadingorFailurestates automatically. - Transforming loaded state without flickering (
RemoteData.map): In client state stores, formatting or filtering already-loaded records (e.g. applying a local text filter to a fetched product catalog) should not reset the UI to a loading state.RemoteData.mapapplies transformations purely to theSuccesspayload while leaving other lifecycle states intact. - Converting asynchronous results (
from.Result,from.Maybe): Bridging async API endpoints and cache lookups into UI component state viaRemoteData.from.ResultandRemoteData.from.Maybeconverts raw outcomes into rendering states in a single line.