A datatype representing fetched data.
If you find yourself continually using Maybe (Result Error a)
to
represent loaded data, or you have a habit of shuffling errors away to
where they can be quietly ignored, consider using this. It makes it
easier to represent the real state of a remote data fetch and handle
it properly.
For more on the motivation, take a look at the blog post How Elm Slays A UI Antipattern.
To use the datatype, let's look at an example that loads News
from a feed.
First you add to your model, wrapping the data you want in WebData
:
type alias Model =
{ news : WebData News }
Then add in a message that will deliver the response:
type Msg
= NewsResponse (WebData News)
Now we can create an HTTP get:
getNews : Cmd Msg
getNews =
Http.get
{ url = "/news"
, expect = expectJson (RemoteData.fromResult >> NewsResponse) decodeNews
}
We trigger it in our init
function:
init : ( Model, Cmd Msg )
init =
( { news = Loading }
, getNews
)
We handle it in our update
function:
update msg model =
case msg of
NewsResponse response ->
( { model | news = response }
, Cmd.none
)
Most of this you'd already have in your app, and the changes are just
wrapping the datatype in Webdata
, and converting the Result from
Http.get
/Http.post
/Http.request
to RemoteData with RemoteData.fromResult
.
Now we get to where we really want to be, rendering the data and handling the different states in the UI gracefully:
view : Model -> Html msg
view model =
case model.news of
NotAsked -> text "Initialising."
Loading -> text "Loading."
Failure err -> text ("Error: " ++ toString err)
Success news -> viewNews news
viewNews : News -> Html msg
viewNews news =
div []
[h1 [] [text "Here is the news."]
, ...]
And that's it. A more accurate model of what's happening leads to a better UI.
Frequently when you're fetching data from an API, you want to represent four different states:
NotAsked
- We haven't asked for the data yet.Loading
- We've asked, but haven't got an answer yet.Failure
- We asked, but something went wrong. Here's the error.Success
- Everything worked, and here's the data.RemoteData Http.Error a
While RemoteData
can model any type of error, the most common
one you'll actually encounter is when you fetch data from a REST
interface, and get back RemoteData Http.Error a
. Because that case
is so common, WebData
is provided as a useful alias.
map : (a -> b) -> RemoteData e a -> RemoteData e b
Map a function into the Success
value.
map2 : (a -> b -> c) -> RemoteData e a -> RemoteData e b -> RemoteData e c
Combine two remote data sources with the given function. The result will succeed when (and if) both sources succeed.
map3 : (a -> b -> c -> d) -> RemoteData e a -> RemoteData e b -> RemoteData e c -> RemoteData e d
Combine three remote data sources with the given function. The result will succeed when (and if) all three sources succeed.
If you need map4
, map5
, etc, see the documentation for andMap
.
andMap : RemoteData e a -> RemoteData e (a -> b) -> RemoteData e b
Put the results of two RemoteData calls together.
For example, if you were fetching three datasets, a
, b
and c
,
and wanted to end up with a tuple of all three, you could say:
merge3 :
RemoteData e a
-> RemoteData e b
-> RemoteData e c
-> RemoteData e ( a, b, c )
merge3 a b c =
map (\a b c -> ( a, b, c )) a
|> andMap b
|> andMap c
The final tuple succeeds only if all its children succeeded. It is
still Loading
if any of its children are still Loading
. And if
any child fails, the error is the leftmost Failure
value.
Note that this provides a general pattern for map2
, map3
, ..,
mapN
. If you find yourself wanting map4
or map5
, just use:
foo f a b c d e =
map f a
|> andMap b
|> andMap c
|> andMap d
|> andMap e
It's a general recipe that doesn't require us to ever have the
discussion, "Could you just add map7
? Could you just add map8
?
Could you just...".
Category theory points: This is apply
with the arguments flipped.
succeed : a -> RemoteData e a
Lift an ordinary value into the realm of RemoteData.
Category theory points: This is pure
.
mapError : (e -> f) -> RemoteData e a -> RemoteData f a
Map a function into the Failure
value.
mapBoth : (a -> b) -> (e -> f) -> RemoteData e a -> RemoteData f b
Map function into both the Success
and Failure
value.
fromTask : Task e a -> Task x (RemoteData e a)
Convert a task to RemoteData.
andThen : (a -> RemoteData e b) -> RemoteData e a -> RemoteData e b
Chain together RemoteData function calls.
withDefault : a -> RemoteData e a -> a
Return the Success
value, or the default.
unwrap : b -> (a -> b) -> RemoteData e a -> b
Take a default value, a function and a RemoteData
.
Return the default value if the RemoteData
is something other than Success a
.
If the RemoteData
is Success a
, apply the function on a
and return the b
.
That is, unwrap d f
is equivalent to RemoteData.map f >> RemoteData.withDefault d
.
unpack : (() -> b) -> (a -> b) -> RemoteData e a -> b
A version of unwrap
that is non-strict in the default value (by
having it passed in a thunk).
fromMaybe : e -> Maybe a -> RemoteData e a
Convert a Maybe a
to a RemoteData value.
fromResult : Result e a -> RemoteData e a
Convert a Result
, probably produced from elm-http, to a RemoteData value.
toMaybe : RemoteData e a -> Maybe a
Convert a RemoteData e a
to a Maybe a
asCmd : Task e a -> Platform.Cmd.Cmd (RemoteData e a)
Convert a web Task
, probably produced from elm-http, to a Cmd (RemoteData e a)
.
append : RemoteData e a -> RemoteData e b -> RemoteData e ( a, b )
Append - join two RemoteData
values together as though
they were one.
If either value is NotAsked
, the result is NotAsked
.
If either value is Loading
, the result is Loading
.
If both values are Failure
, the left one wins.
fromList : List (RemoteData e a) -> RemoteData e (List a)
Convert a list of RemoteData to a RemoteData of a list.
isSuccess : RemoteData e a -> Basics.Bool
State-checking predicate. Returns true if we've successfully loaded some data.
isFailure : RemoteData e a -> Basics.Bool
State-checking predicate. Returns true if we've failed to load some data.
isLoading : RemoteData e a -> Basics.Bool
State-checking predicate. Returns true if we're loading.
isNotAsked : RemoteData e a -> Basics.Bool
State-checking predicate. Returns true if we haven't asked for data yet.
update : (a -> ( b, Platform.Cmd.Cmd c )) -> RemoteData e a -> ( RemoteData e b, Platform.Cmd.Cmd c )
Apply an Elm update function - Model -> (Model, Cmd Msg)
- to any Successful
-ly loaded data.
It's quite common in Elm to want to run a model-update function, over some remote data, but only once it's actually been loaded.
For example, we might want to handle UI messages changing the users settings, but that only makes sense once those settings have been returned from the server.
This function makes it more convenient to reach inside a
RemoteData.Success
value and apply an update. If the data is not
Success a
, it is returned unchanged with a Cmd.none
.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
EnabledChanged isEnabled ->
let
( settings, cmd ) =
RemoteData.update (updateEnabledSetting isEnabled) model.settings
in
( { model | settings = settings }, cmd )
updateEnabledSetting : Bool -> Settings -> ( Settings, Cmd msg )
updateEnabledSetting isEnabled settings =
( { settings | isEnabled = isEnabled }, Cmd.none )
prism : { getOption : RemoteData e a -> Maybe a, reverseGet : a -> RemoteData e a }
A monocle-compatible Prism.
If you use Monocle, you'll want this, otherwise you can ignore it.
The type signature is actually:
prism : Prism (RemoteData e a) a
...but we use the more verbose type here to avoid introducing a dependency on Monocle.