0
votes

I am trying to update my bitcoin price field in my List Token Model in my update function. This is my code I cannot seem to get it to only update the price field. Do I need to access the list elements seeing as my model is list? Is this possible to do using record syntax in elm?

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Bass exposing (style, center, h1)
import Http
import Json.Decode as Decode




main =
  Html.program
    { init = init
    , view = view
    , update = update
    , subscriptions = subscriptions
    }



------------- MODEL


type alias Model =
  { tokens : List Token
  }

init : (Model, Cmd Msg)
init =
  (initialModel , Cmd.none)

initialModel : Model
initialModel =
  { tokens = [Token "Bitcoin" "150" "11000.00"]
  }

type alias Token =
  { name : String
  , holdings : String
  , price : String
}

------------- UPDATE


type Msg
  = FetchDatabasePrice | FetchLivePrice (Result Http.Error String)


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    FetchDatabasePrice ->
      (model, getPrice )
    FetchLivePrice (Ok newPrice) ->
      ( { model | price = newPrice }, Cmd.none )
    FetchLivePrice (Err _) ->
      (model,Cmd.none)


getPrice : Cmd Msg
getPrice  =
  let
    url = "https://api.coinmarketcap.com/v1/ticker/bitcoin/"
    request = Http.get url decodedUrl
  in
    Http.send FetchLivePrice request

decodedUrl : Decode.Decoder String
decodedUrl = Decode.at ["price_usd"] Decode.string
------------- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
  Sub.none



------------- VIEW


view : Model -> Html Msg
view model =
  div []
    [nav
    , div [] [list model.tokens]
    , div [] [ button [onClick (FetchDatabasePrice) ] [text "Fetch Price"] ]
    ]


------BROKEN INTO PIECES---------


nav : Html Msg
nav = div [Bass.style
            [center
            , Bass.h1
            , [("background-color", "black")
            , ("color", "white")
              ]
            ]
           ]
           [ div [] [text "Crypto Nutshell"]]

list : List Token -> Html Msg
list tokens =
  div [Bass.style
        [Bass.left_align
        ]
      ]
    [div [ class "p1"]
       [ table []
          [ thead []
             [ tr []
                [ th [] [text "Name"]
                , th [] [text "Holdings"]
                , th [] [text "Price"]
                , th [] [text "Actions"]
                ]
             ]
             , tbody [] (List.map tokenRow tokens)
          ]
        ]
      ]

tokenRow : Token -> Html Msg
tokenRow token =
  tr []
      [ td [] [ text token.name ]
      , td [] [ text token.holdings ]
      , td [] [ text token.price ]
      ]

This is my error:

-- TYPE MISMATCH ------------------------------------------------------ test.elm

The definition of `update` does not match its type annotation.

50| update : Msg -> Model -> (Model, Cmd Msg)
51| update msg model =
52|>  case msg of
53|>    FetchDatabasePrice ->
54|>      (model, getPrice )
55|>    FetchLivePrice (Ok newPrice) ->
56|>      ( { model | price = newPrice }, Cmd.none )
57|>    FetchLivePrice (Err _) ->
58|>      (model,Cmd.none)

The type annotation for `update` says it always returns:

    ( Model, Cmd Msg )

But the returned value (shown above) is a:

    ( { b | price : String }, Cmd Msg )

Hint: The record fields do not match up. One has tokens. The other has price.



-- TYPE MISMATCH ------------------------------------------------------ test.elm

`model` is being used in an unexpected way.

56|       ( { model | price = newPrice }, Cmd.none )
              ^^^^^
Based on its definition, `model` has this type:

    Model

But you are trying to use it as:

    { b | price : a }

Hint: The record fields do not match up. One has tokens. The other has price.
1

1 Answers

4
votes

The type errors are fundamentally telling you your issue - you are trying to work on a Token, but you don't have one - you have a Model.

How do we get from one to the other? Well. We start with a model, and we can do model.tokens to get a List Token. We then want to modify that list to contain the new tokens, updated. The normal way to do this is with List.map. This operates on each Token and gives us the updated list. Following these steps:

FetchLivePrice (Ok newPrice) ->
  let
    updatePrice = (\token -> { token | price = newPrice })
    updated = List.map updatePrice model.tokens
  in
    ({ model | tokens = updated }, Cmd.none )

Now, the solution I've given is a simple one that will fall apart when you have multiple different tokens (they will all get changed at the same time). As you only have one right now, the same thing could be achieved just by simplifying the model to take only a single token, not a list.

You will need to start identifying which token you are getting the price for so you can update the right one, if you want to be able to use the ability to have multiple tokens.

In reality, you probably want this to end up looking something like:

FetchLivePrice tokenId (Ok newPrice) ->
    ({ model | tokens = tokenUpdatePrice tokenId newPrice model.tokens, Cmd.none)

Where tokenUpdatePrice is a function that manipulates your list (or other data structure - a dictionary might be appropriate, although you might need to store a separate order for presentation) to update the appropriate records. tokenId will be something used to identify the token.