I cannot render a basic input, complains about type mismatch

I’m learning Elm for a side project. I am attempting to show a very simple input box that allows for a new list item to be created with that value but alas I’m getting an error I cannot understand. The symptoms occur around PostIssue and the view that triggers it.

Msg type

type Msg = NewIssue
         | CancelNewIssue
         | PostIssue String

Update function

update : Msg -> Model -> Model
update msg model =
  case msg of
    NewIssue ->
      { model | newIssue = True }

    CancelNewIssue ->
      { model | newIssue = False }

    PostIssue content ->
      let issue = { id = 1 + List.length model.issues |> Debug.toString
                  , subject = content
                  , parent = Nothing
                  }
      in
      { model | issues = model.issues ++ [issue], newIssue = False }

Error

-- TYPE MISMATCH --------------------- /home/jose/workspace/rednast/src/Main.elm

The 3rd element of this list does not match all the previous elements:

43|     [
44|       h1 [] [ text("Project: " ++ model.projectId) ]
45|     , div [] (List.map viewIssue model.issues)
46|>    , input [ onBlur PostIssue ] []
47|     ]

This `input` call produces:

    Html (String -> Msg)

But all the previous elements in the list are:

    Html Msg

Hint: Everything in a list must be the same type of value. This way, we never
run into unexpected values partway through a List.map, List.foldl, etc. Read
<https://elm-lang.org/0.19.1/custom-types> to learn how to “mix” types.

The short answer: The onBlur function needs just a Msg (ie a Msg constructor that takes no arguments) rather than String -> Msg like your PostIssue constructor which needs a string in order to become a real Msg.

The long answer: onBlur does not have access to the value in the input field, so it’s not the way to access what the user has typed in. To get the current value in the input field you’ll want to use onInput (defined in Html.Events and store the value in your Model (in something like a currentIssue record field). You need to store the value after each input event (ie keystroke) since the view function needs to know what to render - if the input after each keystroke is not stored in your model, any new input in the field won’t be rendered. The example in the Elm Guide shows you how to set up a basic text input.

If you want to treat the blur event as a sort of submit action, you should create a separate Msg constructor that takes no input for use with onBlur. Then in your update function, when that message is received it can look up the stored value in your model for the current input and add it to the list of issues (or otherwise handle it as desired).

Thanks, @samjk. I did end up using your suggestion earlier. I learned quickly that onBlur does not get ahold of the input’s text and I’d have to store it somewhere with onInput.

This idea that Elm’s state includes buffer variables to keep track of these things is considerably different from other tools like React or Vue where there are component-level variables that typically store that data. I’ll have to get used to this.

Yeah for me what helped was realizing that those characters have to be stored somewhere…in React it’s in a component and in Elm it’s in the Model. Was weird for me when I learned React as well!

2 Likes

thanks my issue has been fixed.