---
title: Debugging DOM event handlers in Elm
teaser: Elm's DOM event handlers fail silently. Here's how to debug them.
tags: elm,web
author: Joël Quenneville
published_on: 2019-09-25
---

[Custom DOM event handlers] in Elm are made up of 3 parts:

1. The [`Html.Events.on`] function
2. The string name of the [DOM event] you want to react to
3. A [JSON decoder] that gets values out of the event object. The decoded value
   gets sent to your `update` function.

For example:

```elm
Html.Events.on "click" clickDecoder
```

One important aspect of decoders is that they can _fail_. For example, you try
to read a property on the event object that doesn't exist. What should the event
handler send to your `update` function? The `Html.Events` library has opted for
perhaps the simplest solution - ignore events that can't be successfully
decoded.

Authors of custom events have leaned into this behavior, writing decoders that
_purposely fail_. This allows them to [write event handlers like `onEnter`] that
listen to the "input" event but only fires if the Enter key is pressed.

[Custom DOM event handlers]: https://thoughtbot.com/blog/building-custom-dom-event-handlers-in-elm
[`Html.Events.on`]: https://package.elm-lang.org/packages/elm/html/latest/Html-Events#on
[DOM event]: https://developer.mozilla.org/en-US/docs/Web/Events
[JSON decoder]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode
[write event handlers like `onEnter`]: https://thoughtbot.com/blog/advanced-dom-event-handlers-in-elm

## Failures

Silently ignoring events that fail to decode is a feature. Until it's not. If
you accidentally wrote a broken decoder, you won't get any error when if fails.
Instead you'll just have an event handler that doesn't seem to be firing any
events. This problem is particularly likely if you're writing a decoder where
you are purposely failing the decoder but may be doing that in the wrong place.

Ideally, there would be a way to **get some visibility into those failures**.
The problem is that JSON decoders aren't aware of their failures. A decoder that
tries to read a string from a non-existent field can't say "log the error if I
fail".

```elm
brokenDecoder : Decoder String
brokenDecoder =
  Decode.field "doesntExist" Decode.string
```

Normally this isn't a problem because we get back a `Result`, either from
directly decoding some JSON or [indirectly from an HTTP response]. There we can
easily add a `Debug.log` to the `Err` branch.

```elm
case Decode.decodeValue brokenDecoder someJson of
  Ok val    -> -- HANDLE SUCCESS
  Err error -> Debug.log "decode error" error
```

The problem is that when writing a DOM event handler, we don't get access to
that `Result`. We need some way of getting access to it _in the decoder_. Time
to dive into some [decoder fanciness]!

[indirectly from an HTTP response]: https://guide.elm-lang.org/effects/http.html#update
[decoder fanciness]: https://thoughtbot.com/blog/5-common-json-decoders

## Logging Decoder

If we had access to the raw event object we could manually call
`Decode.decodeValue` on it to get the `Result`. But inside a decoder we don't
have access to the event directly. Only whatever properties we've decided to
decode on it. This is where one of the weirder decoders in the `Json.Decode`
library becomes useful.

[`Decode.value`] is a decoder that doesn't do any decoding. Instead, it just gives
you back the unparsed blob of JSON. Rather than asking for a particular field
from the DOM event, we can ask for "all of it". Chain an `andThen` onto it and
now you have a reference to the event that you can use in your code:

```elm
Decode.value
  |> Decode.andThen (\event -> -- YAY!!)
```

The whole point of getting the event was so we could call `Decode.decodeValue`
manually on it so that we could get access to the `Result`. Now we have access
to the error and can log it. Success!

```elm
Decode.value
  |> Decode.andThen
    (\event ->
      case Decode.decodeValue brokenDecoder event of
        Ok val    -> -- HANDLE SUCCESS
        Err error -> Debug.log "decode error" error
      )
```

[`Decode.value`]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#value

## Final product

We still want to return a decoder so we re-wrap the values from our `Ok` and
`Err` branches with `Decode.succeed` and `Decode.fail` respectively.
Parameterize it to make it generic and we end up with a function that looks
like this:

```elm
loggingDecoder : Decoder a -> Decoder a
loggingDecoder realDecoder =
  Decode.value
    |> Decode.andThen
      (\event ->
        case Decode.decodeValue realDecoder event of
          Ok decoded ->
            Decode.succeed decoded

          Err error ->
            error
              |> Decode.errorToString
              |> Debug.log "decoding error"
              |> Decode.fail
      )
```

Now we can wrap decoders we'd like to inspect with it. Any time the event
handler drops an event because of a failed decoder you'll see the error show up
in your browser console.

```elm
Html.Events.on "click" (loggingDecoder brokenDecoder)
```

You can see it in action in this [working example].

[working example]: https://ellie-app.com/4mgzQrXfP4Fa1
