Unable to trigger response message when sending initial message - elm

I am unable to trigger a response message when sending an initial message.
I have a button:
button
[ class "register"
, value "Create Account"
, onClick Submit
]
I have the following messages:
type Msg
= Submit
| Response (Result Http.Error JsonProfile)
The message handler that is invoked via button click is the following:
update : Msg -> Form -> ( Form, Cmd Msg )
update msg model =
case msg of
Submit ->
( model, runtime.tryRegister model Response )
...
Here's the other message handlers:
update : Msg -> Form -> ( Form, Cmd Msg )
update msg model =
case msg of
Submit ->
( model, runtime.tryRegister model Response )
Response (Ok json) ->
( model, Navigation.load <| "/#/portal/1" )
Response (Err error) ->
( model, Cmd.none )
My tryRegister implementation is the following:
tryRegister : Form -> (Result Http.Error JsonProfile -> msg) -> Cmd msg
tryRegister form msg =
let
jsonProfile =
JsonProfile 1 form.firstName form.lastName form.email
newMsg v =
msg
in
Cmd.map (newMsg <| Result.Ok jsonProfile) Cmd.none
Here's the client code to the elm module depicted above:
onRegistration : Registration.Msg -> Model -> ( Model, Cmd Msg )
onRegistration subMsg model =
let
( form, _ ) =
Registration.update subMsg model.registration
in
case subMsg of
Registration.Submit ->
( { model | registration = form }, Cmd.none )
Registration.Response result ->
case result of
Result.Ok jsonProfile ->
let
newUser =
jsonProfileToProvider jsonProfile
newState =
{ model
| registration = form
, portal =
{ initPortal
| provider = newUser
, requested = Domain.EditProfile
, linksNavigation = False
, sourcesNavigation = False
}
}
in
( newState, Navigation.load <| "/#/portal/" ++ getId newUser.profile.id )
Result.Err _ ->
( model, Cmd.none )
Expectation:
I expect that when I click the button, that navigation takes place.
However, nothing happens and I don't understand why.
Video
Source code is here.

Apparently Cmd.map (...) Cmd.none is not sufficient to force another update cycle. You can force an update cycle by sending an always-succeeding task with Task.perform.
tryRegister : Form -> (Result Http.Error JsonProfile -> msg) -> Cmd msg
tryRegister form msg =
JsonProfile 1 form.firstName form.lastName form.email
|> Result.Ok
|> msg
|> Task.succeed
|> Task.perform identity
Note: There are good reasons not to do this, as outlined here, but we'll ignore those for now to fit the framework you've outlined
However, that alone will not make your code work. You have a nested update call which ignores the Cmd returned from Register.update:
( form, _ ) =
Registration.update subMsg model.registration
That underscore has the effect of blocking all commands generated from the child update. You will need to retain that child Cmd, map it to the parent Cmd, and return it instead of Cmd.none inside all onRegistration cases. For example:
onRegistration : Registration.Msg -> Model -> ( Model, Cmd Msg )
onRegistration subMsg model =
let
( form, subcmd ) =
Registration.update subMsg model.registration
regcmd =
Cmd.map OnRegistration subcmd
in
case subMsg of
Registration.FirstNameInput _ ->
( { model | registration = form }, regcmd )
...

Cmd.none is used in tryRegister function, which does nothing. I think you should use Http.send which actually fires the message loop after the http request completes.
The shortest example...,
update msg model =
case msg of
Submit ->
(model, Http.send Response request)
Response ... ->
...

Related

Elm: How to use data from one HTTP request in subsequent requests

I am new to Elm and just read the docs (https://guide.elm-lang.org/). I am modifying an example from there and playing around.
What I want to do is to hit an endpoint which will give me a list of IDs. Later I want to hit another endpoint with each of these IDs and display the results.
https://hacker-news.firebaseio.com/v0/topstories.json -
This endpoint has a list of IDs.
https://hacker-news.firebaseio.com/v0/item/[ID].json -
This endpoint will give the details of the story of given ID.
With what I have till now, I can get the list of all IDs separately and I can get each story separately (hard-coded ID) and display them. But what I am trying achieve here is to
get the list of IDs (500 of them) from endpoint 1
get first 5 of the stories by hitting endpoint 2
have a "load more" button which will load 5 more and so on
I am not sure how to do this. Any help is greatly appreciated.
Thanks
You can fire the second request when you handle the response from the first endpoint. Something like:
type Msg
= GotIds (Result Http.Error (List Int))
| GotStory (Result Http.Error (String))
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
GotIds result ->
case result of
Ok (first::rest) ->
({ model | ids = first::rest }, getStory first)
Ok _ ->
(model, Cmd.none)
Err _ ->
({ model | story = "ERROR"}, Cmd.none)
GotStory result ->
({model | story = Result.withDefault "None" result}, Cmd.none)
If you want to fire multiple Cmd at the same time, you can use Cmd.batch
Here is an Ellie that gets the ids from the first request and then fetches the title for the first ID.
You will want to create a custom type and decoder for each post.
For posterity's sake, here is all of the code from the Ellie:
module Main exposing (main)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
import Http
import Json.Decode exposing (Decoder, field, int, list, string )
type alias Model =
{ ids : List Int
, story : String
}
initialModel : Model
initialModel =
{ ids = []
, story = "None"
}
type Msg
= GotIds (Result Http.Error (List Int))
| GotStory (Result Http.Error (String))
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
GotIds result ->
case result of
Ok (first::rest) ->
({ model | ids = first::rest }, getStory first)
Ok [] ->
(model, Cmd.none)
Err _ ->
({ model | story = "ERROR"}, Cmd.none)
GotStory result ->
({model | story = Result.withDefault "None" result}, Cmd.none)
view : Model -> Html Msg
view model =
div []
[ text model.story
]
main : Program () Model Msg
main =
Browser.element
{ init = init
, view = view
, update = update
, subscriptions = (\_ -> Sub.none)
}
init : () -> (Model, Cmd Msg)
init flags =
(initialModel, getIds)
getIds : Cmd Msg
getIds =
Http.get
{ url = "https://hacker-news.firebaseio.com/v0/topstories.json"
, expect = Http.expectJson GotIds (list int)
}
getStory : Int -> Cmd Msg
getStory id =
Http.get
{ url = "https://hacker-news.firebaseio.com/v0/item/" ++ String.fromInt id ++ ".json"
, expect = Http.expectJson GotStory (field "title" string)
}

Handling messages when composing with list of widgets in Elm

In a few guides, message handling in parent components is made like this;
type Msg
= NavMsg Nav.Msg
| SidebarMsg Sidebar.Msg
| WidgetMsg Widget.Msg
And parent components handle them in updates with:
update : Msg -> AppModel -> (AppModel, Cmd Msg)
update message model =
case message of
WidgetMsg subMsg ->
let
(updatedWidgetModel, widgetCmd) =
Widget.update subMsg model.widgetModel
in
({ model | widgetModel = updatedWidgetModel }, Cmd.map WidgetMsg widgetCmd)
_ ->
However, I couldn't find a simple way to do the same if the child components are inside a list. How can I tell the correct sub component to react to a message directed to him?
I thought of adding the component object to the message:
type Msg
= MessageToParent
| MessageToChild Child Child.Msg
But this seems very inefficient if the Child component is big, and still gives me trouble when trying to delegate the inner Child.Msg to the right Child.
What's the best way to handle message passing to a list of components?
I would suggest using an array (or Dict) and code like this
type Msg
= WidgetMsg Int Widget.Msg
update : Msg -> AppModel -> (AppModel, Cmd Msg)
update message model =
case message of
WidgetMsg idx subMsg ->
model.widgetModels
|> Array.get idx
|> Maybe.map (Widget.update subMsg)
|> Maybe.map (\(m,c) ->
({ model | widgetModels = Array.set idx m model.widgetModels }
, Cmd.map Widgetmsg idx c)
)
|> Maybe.withDefault (model, Cmd.none)
_ ->

Reading a FileReader type with a Maybe in elm

(This is related to Initializing an empty file value in Elm )
I am using Elm (0.18) and imported simonh1000's FileReader library. To store a file value, we use the following json type:
type alias FileContentArrayBuffer =
Value
and I structure my model thusly:
type alias Model =
{
username : String
, filecontent: Maybe FileContentArrayBuffer
}
initialModel : Model
initialModel =
{
username = "mark"
, filecontent = Nothing
}
When a file is dropped into place, getFileContents is called. The relevant functions and msg's are as follows:
getFileContents : NativeFile -> Cmd Msg
getFileContents nf =
FileReader.readAsArrayBuffer nf.blob
|> Task.attempt OnFileContent
type Msg
...
| OnFileContent (Result FileReader.Error (Maybe FileContentArrayBuffer))
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
...
OnFileContent res ->
case res of
Ok (Just filecontent) ->
( { model | filecontent = filecontent }, Cmd.none )
Ok Nothing ->
Debug.crash "No Content"
Err err ->
Debug.crash (toString err)
When I compile I get this error:
The right side of (|>) is causing a type mismatch.
56| FileReader.readAsArrayBuffer nf.blob
57|> |> Task.attempt OnFileContent
(|>) is expecting the right side to be a:
Task.Task FileReader.Error FileReader.FileContentArrayBuffer -> a
But the right side is:
Task.Task FileReader.Error (Maybe FileReader.FileContentArrayBuffer)
-> Cmd Msg
Not sure why, given that I have included the Maybe in my Type and provided cases. Any ideas?
If you define Msg like this instead:
type Msg
...
| OnFileContent (Result FileReader.Error FileContentArrayBuffer)
Then your update case can set the file value when successful or set to Nothing in failure:
OnFileContent res ->
case res of
Ok filecontent ->
( { model | filecontent = Just filecontent }, Cmd.none )
Err err ->
( { model | filecontent = Nothing }, Cmd.none )
Note that Debug.crash should be avoided at all costs. It really is just there for temporary debugging. It would be better perhaps to add an error message property on your model to notify the user of a problem.

Elm - Turn Msg into Cmd Msg

I'm trying to modify a simple app from the elm-lang tutorial to first update the model, then trigger another update.
update msg model =
case msg of
MorePlease ->
(model, getRandomGif model.topic)
NewGif (Ok newUrl) ->
( { model | gifUrl = newUrl }, Cmd.none)
NewGif (Err _) ->
(model, Cmd.none)
-- my addition
NewTopic newTopic ->
({ model | topic = newTopic}, MorePlease)
This fails in the compiler because the NewTopic branch:
The 3rd branch has this type:
( { gifUrl : String, topic : String }, Cmd Msg )
But the 4th is:
( { gifUrl : String, topic : String }, Msg )
So my Msg needs to be type Cmd Msg. How can I turn" my Msg into a Cmd Msg?
note: I recognize there is a simpler way to make this change, but I'm trying to understand Elm more fundamentally
There is really no need to turn Msg into a Cmd Msg. Remember that update is just a function, so you can call it recursively.
Your NewTopic case handler can be simplified to this:
NewTopic newTopic ->
update MorePlease { model | topic = newTopic}
If you really truly wanted the Elm Architecture to fire off a Cmd for this scenario, you could do a simple map of Cmd.none to your desired Msg:
NewTopic newTopic ->
({ model | topic = newTopic}, Cmd.map (always MorePlease) Cmd.none)
(not actually recommended)
Add the following function:
run : msg -> Cmd msg
run m =
Task.perform (always m) (Task.succeed ())
Your code would then turn into:
NewTopic newTopic ->
({ model | topic = newTopic}, run MorePlease)

Elm 0.17: HTTP request on init

I have followed this blog post to make an HTTP call when I click a button. I would like to load that data on init, like this question asks, but the solutions given there aren't clear enough for me and my slightly different code.
The app update and init code:
init : (Model, Cmd Msg)
init =
( initialModel, Cmd.none )
-- UPDATE
type Msg
= ArticleListMsg ArticleList.Msg
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
ArticleListMsg articleMsg ->
let (updatedModel, cmd) = ArticleList.update articleMsg model.articleListModel
in ( { model | articleListModel = updatedModel }, Cmd.map ArticleListMsg cmd )
The ArticleList update code:
module ArticleList exposing (..)
-- UPDATE
type Msg
= NoOp
| Fetch
| FetchSucceed (List Article.Model)
| FetchFail Http.Error
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
NoOp ->
(model, Cmd.none)
Fetch ->
(model, fetchArticles)
FetchSucceed articleList ->
(Model articleList, Cmd.none)
FetchFail error ->
case error of
Http.UnexpectedPayload errorMessage ->
Debug.log errorMessage
(model, Cmd.none)
_ ->
(model, Cmd.none)
-- HTTP calls
fetchArticles : Cmd Msg
fetchArticles =
let
url = "/api/articles"
in
Task.perform FetchFail FetchSucceed (Http.get decodeArticleFetch url)
I've tried sending the command on init instead of Cmd.none:
init : (Model, Cmd Msg)
init =
( initialModel, ArticleList.Fetch )
But the compiler complains:
The type annotation is saying:
( Model, Cmd Msg )
But I am inferring that the definition has this type:
( Model, ArticleList.Msg )
I've also tried passing the function:
init =
( initialModel, ArticleList.fetchArticles )
But the compiler complains:
Cannot find variable `ArticleList.fetchArticles`.
30| ( initialModel, ArticleList.fetchArticles )
^^^^^^^^^^^^^^^^^^^^^^^^^
`ArticleList` does not expose `fetchArticles`.
How can I send the correct message to make the HTTP call on init?
Your init should be
init : (Model, Cmd Msg)
init =
( initialModel, Cmd.map ArticleListMsg ArticleList.fetchArticles )
ArticleList.fetchArticles has type Cmd ArticleList.Msg. You need to use Cmd.map to map that value to type Cmd Msg using the ArticleListMsg type constructor.