Objects into JSON decoder through Elm ports - elm

I am passing an array of objects via ports into my Elm app. An example of an one of the objects in the array is:
{
FullName: 'Foo Bar',
Location: 'Here'
}
As you can see the keys in the object start with a capital, so I need to decode these in Elm. In my Elm code I have a type for the Person
type alias Person =
{ fullName : String
, location : String
}
and the port:
port getPeople : (List Json.Decode.Value -> msg) -> Sub msg
Finally I have a decoder (I am using Elm Decode Pipeline) to parse the data into the Person type.
peopleDecoder : Decoder Person
peopleDecoder =
decode Person
|> required "FullName" string
|> required "Location" string
My question is how do I map the incoming port data into the Person type? I know I could do this in JS but I'd rather do it in my Elm code.

Json.Decode.decodeValue can decode a Json.Decode.Value, but it returns a Result String (List Person).
If you defined your Msg like this:
type Msg
= GetPeople (Result String (List Person))
You could set up your subscription like this:
port getPeople : (Json.Decode.Value -> msg) -> Sub msg
subscriptions : Model -> Sub Msg
subscriptions model =
getPeople (GetPeople << decodeValue (list peopleDecoder))
(Note that the first argument in the port has been changed to just a Value instead of List Value)

Related

This `div` call produces: Html (String -> Msg) But the type annotation on `view` says it should be: Html Msg

I'm currently learning elm, I just stumbled on this problem where the div returns a Html (String -> Msg) instead of Html Msg.
error message I'm receiving
This div call produces:
Html (String -> Msg)
But the type annotation on view says it should be:
Html Msg
type alias Model =
{
firstNum: String,
secondNum: String,
answer: String
}
init: Model
init = { firstNum = "",
secondNum = "",
answer = ""}
type Msg =
Add String| Minus String
update: Msg -> Model -> Model
update msg model =
case msg of
Add x -> { model | answer = x}
Minus y -> { model | answer = y}
view : Model -> Html Msg
view model =
div []
[
input [ placeholder "Text to reverse", value model.firstNum] [],
button [onClick Add] [text "add"],
div [] [text model.answer]
]
main =
Browser.sandbox
{ init = init,
update = update,
view = view
}
You define the Msg type as
type Msg =
Add String| Minus String
with Add taking a String argument, but when you use it here:
button [onClick Add] [text "add"],
you're not giving it any argument at all.
The underlying issue seems to be that your mental model of the Elm Architecture is wrong. You seem to consider messages as "operations" or function calls rather than events, where Add is a function that takes an argument to apply to the model.
You should instead consider a message as a description of what triggered it. Instead of Add String, you might call it AddButtonClicked, with no arguments (in this case). Then have the update function do what it should based on what's in the model alone, which I'm guessing is an arithmetic operation on firstNum and secondNum.
But you're also not populating those fields. To do so you need to use the onInput event, which does ask for a message that takes a String. You might add a new message FirstNumChanged String for example, then use it with input like this:
input [ placeholder "Text to reverse", onInput FirstNumChanged, value model.firstNum] [],
I'll leave it to you to figure out how to handle it in update.

TYPE MISMATCH - This function cannot handle the argument sent through the (|>) pipe:

I am a super elm begginer and trying to make app.
Currently I am struggling to make landing page and http request to a server.
But, I am stuck here...
I have init function something like this below.
init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
Model key TopPage
|> goTo (Route.parse url)
The definition of my Model is below.
-- MODEL
type alias Model =
{ key : Nav.Key
, page : Page
, name : String
, tags : List Tag
, jwt : String }
and, goTo function is below.
goTo : Maybe Route -> Model -> ( Model, Cmd Msg )
goTo maybeRoute model =
case maybeRoute of
Nothing ->
( { model | page = NotFound }, Cmd.none )
Just Route.Top ->
( { model | page = TopPage }, Cmd.none )
...
type Route is below.
type Route
= Top
| User String
| Repo String String
parse : Url -> Maybe Route
parse url =
Url.Parser.parse parser url
parser : Parser (Route -> a) a
parser =
oneOf
[ map Top top
, map User string
, map Repo (string </> string)
]
but following error has occured.
-- TYPE MISMATCH -------------------------------------------------- src/Main.elm
This function cannot handle the argument sent through the (|>) pipe:
54| Model key TopPage
55| |> goTo (Route.parse url)
^^^^^^^^^^^^^^^^^^^^^
The argument is:
String -> List Tag -> String -> Model
But (|>) is piping it a function that expects:
Model
What did I make mistake here?....
Your Model type has five fields, but in the line
Model key TopPage
you are only providing values for the first two of the five. You are missing values for the name, tags and jwt fields. Provide values for these and the problem should go away.
When you declare a type alias such as Model, Elm creates a constructor function also named Model. Elm functions support partial application, in that if you pass in values for some but not all of the arguments, you end up with a function that takes in the rest of the values. You provided two arguments, so you end up with a function that takes three arguments and returns a Model.
There are two ways of building a value of a type. Given a simple example of a Person type alias:
type alias Person = { name : String, age : Int }
You can construct a value by specifying all fields (note that you don't have to specify Person in the constructor; Elm's compiler is smart enough to know it by its shape):
jane : Person
jane = { name = "Jane", age = 35 }
Or you can build a value by using the type name and specify each field's values in the order in which they were defined. In this style, you can think of Person acting like a function with two parameters that returns a Person value.
jane : Person
jane = Person "Jane" 35
In each case, you have to specify all fields of the type when you construct it in order to obtain a complete Person value. However, that is not the complete story. It is possible to leave off the age parameter when constructing a Person, but the result isn't a Person, it's a function that takes an age and returns a Person. In other words,
janeAged : Int -> Person
janeAged = Person "Jane"
You can strip off as many parameters from the end as you'd like to make more variations on that constructor, even stripping out all parameters:
somebody : String -> Int -> Person
somebody = Person
Back to your example. You are constructing a Model value by only specifying two parameters (Model key TopPage). The value of that expression does not result in a Model, but in a function that takes three more parameters to create a Model. And that's why the error message indicated you need three parameters to construct a model.
You need to specify all values of Model when creating it.

Elm Http.post needs to produces Http.Request Int but send needs Http.Request String

I decode response from Http.post request, where return type is Integer:
responseDecoder : Decoder Int
responseDecoder =
field "data" (field "createDeadline" (field "id" int))
My problem is that I use Http.send which needs String:
createDeadline value =
Http.send Resolved (Http.post deadlineUrl (encodeBody value |> Http.jsonBody) responseDecoder)
And I dont know how to change the return type. My error message is following:
The 2nd argument to `send` is not what I expect:
113| Http.send Resolved (Http.post deadlineUrl (encodeBody value |> Http.jsonBody) responseDecoder)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This `post` call produces:
Http.Request Int
But `send` needs the 2nd argument to be:
Http.Request String
Hint: I always figure out the argument types from left to right. If an argument
is acceptable, I assume it is “correct” and move on. So the problem may actually
be in one of the previous arguments!
Hint: Want to convert an Int into a String? Use the String.fromInt function!
Can anyone help with this? I'm just playing around with Elm, but I got stuck here.
My problem is that I use Http.send which needs String
According to the docs, send function requires the argument to be of type Request a, where a can be any type (Int or String or something else).
The problem that you have is just what the hint in the compile error says:
Hint: I always figure out the argument types from left to right. If an argument
is acceptable, I assume it is “correct” and move on. So the problem may actually
be in one of the previous arguments!
Thus, it seems that you've already defined somewhere, that you're expecting String and the compiler infered the type to Request String. For example, you might have the Resolved defined something like the following:
type Msg = Resolved (Result Http.Error String)
And the compiler inferred the polymorphic type of send : (Result Error a -> msg) -> Request a -> Cmd msg to something specific, because it already sees that the first argument is or type (Result Error String -> msg):
send : (Result Error String -> msg) -> Request String -> Cmd msg
So, in this case the solution is either to change the expecting type:
type Msg = Resolved (Result Http.Error Int)
Or change the decoder and decode the response to String:
responseDecoder : Decoder String
responseDecoder =
Json.Decode.map String.fromInt (field "data" (field "createDeadline" (field "id" int)))

Avoid Http Race Condition in Elm

Let's assume we have a text input field and on every change of its content we send an Http request to a search API. Now, we don't have any guarantee that the Http responses get back to elm in the same order that we sent the requests.
What's the easiest way to make sure we react to the response corresponding to the latest request – rather than the latest response, which might correspond to an outdated search string? Is there an easy way to attach the query string to the message returned by Elm's http effect? Or any other way we can link the response to the request by which it was triggered?
I'd like to avoid including the query in the response of the search API if possible. Another remedy would be to debounce the search, but that would just decrease the probability of using the wrong response, whereas we'd like to eliminate it.
Thanks for your help!
Example:
import Html
import Html exposing (..)
import Html.Events exposing (onClick, onInput)
import Http
import Json.Decode as Decode
main = Html.program
{ init = ( { searchText = "", result = "" }, Cmd.none )
, update = update
, subscriptions = (\model -> Sub.none)
, view = view
}
type alias Model =
{ searchText : String
, result: SearchResult
}
type alias SearchResult = String
type Msg
= NewSearchText String
| ReceivedResponse (Result Http.Error SearchResult)
update msg model =
case msg of
NewSearchText newText ->
( { model | searchText = newText}
, getSearchResult newText
)
ReceivedResponse (Result.Ok response) ->
( { model | result = response }
, Cmd.none
)
ReceivedResponse (Result.Err error) ->
Debug.crash <| (toString error)
getSearchResult : String -> Cmd Msg
getSearchResult query =
let
url = "http://thebackend.com/search?query=" ++ query
request : Http.Request SearchResult
request = Http.get url Decode.string
in
Http.send ReceivedResponse request
view model =
div []
[ Html.input [onInput (\text -> NewSearchText text)] []
, Html.text model.result
]
Yes, it is possible to attach the query string to the response. First, augment your message type to handle the additional data:
type Msg
= NewSearchText String
| ReceivedResponse String (Result Http.Error SearchResult)
Then, change your Http.send call to attach the query text to the ReceivedResponse message:
Http.send (ReceivedResponse query) request
Finally, in your update, grab the query in your pattern match on the resulting Msg:
case msg of
ReceivedResponse query (Ok response) ->
...
ReceivedResponse query (Err err) ->
...
Why does this work?
The Http.send function's first argument can be an arbitrary function that consumes a Result Http.Error SearchResult and turns it into a Msg. In your original code, that function is just ReceivedResponse, the Msg constructor. When the Msg type is updated so that ReceivedResponse takes two arguments, the ReceivedResponse constructor function becomes a curried two-argument function, and ReceivedResponse "some query here" is a one-argument function that takes in a Result and returns a Msg.
Here's one way:
Add two integers to your model:
requestsSent : Int -- the number of requests made.
lastReceived : Int -- the latest request that you've processed.
Modify ReceivedResponse to have an Int as the first value:
| ReceivedResponse Int (Result Http.Error SearchResult)
Now, whenever you make a request, increment requestsSent by 1 in the model and "tag" the request by partially applying ReceivedResponse:
Http.send (ReceivedResponse model.requestsSent) request
In your update function, check if the Int in the ReceivedResponse is greater than lastReceived or not. If it is, process it, and set the value of lastReceived to this response's Int. If it isn't, discard it, because you've already processed a newer request.

Msg's with extra variables in elm inputs

I am trying to partition my Msg values in Elm (0.18) into different types. I'd like it to conform to this kind of typing:
type MsgSession
= LogIn
| LogOut
type MsgSettings
= SetUsername String
= SetPassword String
type Msg
= SessionMsg MsgSession
| SettingsMsg MsgSettings
...
My update function looks like this:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
SessionMsg sessionMsg ->
sessionUpdate sessionMsg model
SettingsMsg settingsMsg ->
settingsUpdate settingsMsg model
...
...which means that I have to define the child updates as well...
sessionUpdate : MsgSession -> Model -> ( Model, Cmd Msg )
sessionUpdate msg model =
case msg of
LogIn ->
-- do stuff
LogOut ->
-- do stuff
My LogOut event looks like this, and works fine:
button [onClick (SessionMsg LogOut)] [text "Log Out"]
But once there is a variable involved with the event, it doesn't work. I have a similar setup for settingsUpdate:
settingsUpdate : MsgSettings -> Model -> ( Model, Cmd Msg )
settingsUpdate msg model =
case msg of
SetUsername string ->
...
SetPassword string ->
...
But I can't get onInput to send as a variable. For example, this code
input [onInput SettingsMsg SetUsername] []
Yields this error:
Function `onInput` is expecting 1 argument, but was given 2.
483| onInput SettingsMsg SetUsername
Parentheses also don't work.
input [onInput (SettingsMsg SetUsername)] []
yields
The argument to function `SettingsMsg` is causing a mismatch.
483| SettingsMsg SetUsername)
^^^^^^^^^^^
Function `SettingsMsg` is expecting the argument to be:
MsgSettings
But it is:
String -> MsgSettings
What do I have to pass into onInput to make this work?
You should be able to use composition:
input [onInput (SettingsMsg << SetUsername)] []
Which, if you're more comfortable with explicit lambdas, looks like this:
input [onInput (\name -> SettingsMsg (SetUsername name))] []