Skip to content

Instantly share code, notes, and snippets.

@Chadtech
Last active April 23, 2022 13:30
Show Gist options
  • Save Chadtech/58d4a5a48122833fca284088659da68b to your computer and use it in GitHub Desktop.
Save Chadtech/58d4a5a48122833fca284088659da68b to your computer and use it in GitHub Desktop.

Only have one outgoing port, and only one incoming port

If you have many ports, they could be all over your project, and therefore hard to manage and easy for forget about. Keep track of all of them by representing your outgoing ports in a union type like..

type JsMsg
    = Download
    | Login Login.Payload
    | Logout
    | Track Tracking.Payload

And sending them by

send : JsMsg -> Cmd msg
send jsMsg =
    case jsMsg of
        Download ->
            toCmd
                "Download"
                Encode.null

        -- ..


toCmd : String -> Encode.Value -> Cmd msg
toCmd type_ payload =
    [ ( "type", Encode.string type_ )
    , ( "payload", payload )
    ]
        |> Encode.object
        |> toJs


port toJs : Value -> Cmd msg

port fromJs : (Value -> msg) -> Sub msg

On the JS side of things you can listen for these messages like this

switch (msg.type) {
    case "Download": 
        download();
        break;
    
    case "login user": {
        ClientApi.login({
            onSuccess: function(user) {
                app.ports.fromJs.send({
                    type: "login succeeded",
                    payload: user
                })
            },
            onFailure: function(err) {
                app.ports.fromJs.send({
                    type: "login failed",
                    payload: String(err)
                })
            }
        });
        break;
    
    // ..

Then to listen for these events, you need a Msg decoder. This is kind of how your Msg.elm file should look:

type Msg
    = LoginFinished (Result String User)
    | LogoutFinished (Result String ())
    | DataFetched (Result String Data)
    -- ..
    | MsgDecodeFailed String
    

-- DECODER --


decode : Value -> Msg
decode json =
    case Decode.decodeValue decoder json of
        Ok msg ->
            msg

        Err err ->
            MsgDecodeFailed err


decoder : Value -> Decoder Msg
decoder =
    Decode.field "type" Decode.string
        |> Decode.andThen 
            (Decode.field "payload" << toMsg)


toMsg : String -> Decoder Msg
toMsg type_ =
    case type_ of
        "login succeeded" ->
            User.decoder
                |> Decode.map (Ok >> LoginFinished)

        "login failed" ->
            Decode.string
                |> Decode.map (Err >> LoginFinished)

        "logout succeeded" ->
            Decode.succeed (LogoutFinished (Ok ()))

        "logout failed" ->
            Decode.string
                |> Decode.map (Err >> LogoutFinished)

        "retreive data succeeded" ->
            Data.decoder
                |> Decode.map (Ok >> DataFetched)

        "retreive data failed" ->
            Decode.string
                |> Decode.map (Err >> DataFetched)

        _ ->
            Decode.fail ("unrecognized msg type : " ++ type_)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment