RemoteData — loading states
Every async data fetch has exactly four moments: before it starts, while it’s loading, when it
fails, and when it succeeds. That’s the whole picture. But it’s common to spread these four states
across separate variables that can get out of sync and produce combinations that shouldn’t exist.
RemoteData<E, A> gives each state a name and keeps them mutually exclusive — only one is active at
any given time.
stateDiagram-v2 direction TB [*] --> NotAsked NotAsked --> Loading : fetch starts Loading --> Success : data arrives Loading --> Failure : request fails Failure --> Loading : retry Success --> Loading : refresh
The problem with flag soup
Section titled “The problem with flag soup”A typical approach to loading states looks like this:
Three separate pieces of state, but they’re not actually independent — only certain combinations are
meaningful. The type allows loading: true and error: "timeout" at the same time, which is
contradictory. Nothing prevents you from forgetting to reset error when a new request starts, or
showing stale data while loading is true.
The RemoteData approach
Section titled “The RemoteData approach”RemoteData makes the states explicit and mutually exclusive. There’s one value with one state at a
time:
Each state is represented once, and they can’t overlap. The type system prevents you from combining them incorrectly.
sequenceDiagram
participant U as user action
participant C as your component
participant S as server
C->>C: notAsked
U->>C: click "Load"
C->>C: loading
C->>S: fetch /users/123
alt success
S-->>C: 200 OK { name: "Alice" }
C->>C: success(user)
else failure
S-->>C: 404 Not Found
C->>C: failure("Not found")
end
Creating RemoteData values
Section titled “Creating RemoteData values”Rendering all four states with match
Section titled “Rendering all four states with match”match is the primary way to consume a RemoteData. It requires a handler for every state, so the
compiler ensures you’ve covered all cases:
Because all four branches are required, there’s no way to accidentally skip the loading state or
forget to handle errors. The type checker will tell you if a case is missing. fold is the
positional form — notAsked, loading, failure, success — if you’d rather not name the cases.
Transforming the success value with map
Section titled “Transforming the success value with map”map transforms the value inside Success, leaving all other states unchanged:
This lets you transform data as part of a pipeline without breaking out of the RemoteData context:
Transforming errors with mapError
Section titled “Transforming errors with mapError”mapError transforms the error inside Failure, leaving other states unchanged:
Useful for normalizing error types from different sources before they reach your rendering logic.
Chaining dependent fetches
Section titled “Chaining dependent fetches”chain sequences a second fetch that depends on the result of the first. If the current state is
Success, it passes the value to the function and returns whatever that produces. All other states
pass through:
If userData is Loading, Failure, or NotAsked, the chain step is skipped and that state
propagates.
Recovering from failures
Section titled “Recovering from failures”recover provides a fallback RemoteData when the current state is Failure. Unlike
Result.recover, it receives the error value so you can use it in the recovery logic. The fallback
can produce a different success type, widening the result to RemoteData<E, A | B>:
Extracting the value
Section titled “Extracting the value”getOrElse — returns the success value or a default thunk () => B for any other state. The
thunk is only called when the value is not Success. The default can be a different type, widening
the result to the union of both:
Converting to other types
Section titled “Converting to other types”When you need to work with a part of the system that uses Maybe or Result, you can convert.
toMaybe maps Success to Some and everything else to None. toResult maps Success to
Ok and Failure to Err; NotAsked and Loading both become Err using a fallback you
provide — they’re both “not yet succeeded”:
If you need to distinguish NotAsked from Loading after conversion, keep using RemoteData
directly — both states collapse into Err and the distinction is lost.
When to use RemoteData
Section titled “When to use RemoteData”Use RemoteData when:
- You’re displaying fetched data in a UI and need to handle all loading states explicitly
- You want the type system to prevent invalid state combinations like simultaneous loading and error
- You want a single value in state instead of three separate flags
It’s also useful outside UI contexts — any time you’re tracking the lifecycle of an async operation and need to distinguish “hasn’t started” from “in progress” from “done”.