Event “Middleware” in JavaScript

Mike Schutte
Vue.js Developers
Published in
3 min readMar 12, 2019

React was my first front-end love. We had to break up when I moved jobs and started seeing Vue. Of course familiarity bias had me missing React and thinking it better, but eventually I came around to enjoying Vue, even preferring it in certain contexts.

One of those preferred contexts is event modifications on event listeners.

<button @click.prevent="handleClick">Click Me</button>

Preventing the event’s default has never looked better. What if you need to also stopPropagation? No worries.

<button @click.prevent.stop="handleClick">Click Me</button>

Glorious.

I hopped back into a React app recently and found the need to manually handle this behavior…barbaric.

const handleClick = e => {
e.preventDefault()
...
}
...
return <button click={handleClick}>Click Me</button>

Obviously not a huge deal in the grand scheme, but something feels off to me about having to do some side-effect-y stuff before I can get to the real purpose of my function. I want my logic to be less coupled to the event when possible. For repeated intervention like this, the duplication in Vue is more declarative and feels like config than logic. Every listener just gets arbitrary amounts of modifiers chained on.

@click.prevent
@click.stop.prevent
@keydown.enter

So on an so forth.

In React I want to have the same declarative ability to state my interventions with the event object, not have to manually send a message to it with dot-notation every time.

It occurred to me: what if we just treat an event object like we would an HTTP request object that gets modified and updated through a series of functions a la Node/Express or the lovely conn pattern in Phoenix?

Here is what I came up with:

const sendToEvent = (message, ...args) => fn => e {
e[message](...args)
return fn(e)
}
const stop = sendToEvent("stopPropagation")
const prevent = sendToEvent("preventDefault")

Then in your react code you can declare your event interventions prior to invoking your core business logic:

<button click={stop(handleClick)}>Click Me</button>
<button click={prevent(stop(handleClick))}>Click Me Too</button>

For more predicate oriented behavior, you could use a similar pattern:

const isKey = key => fn => e => {
if (e.key === key) {
return fn(e)
}
return e
}
const isEnter = isKey("Enter")
const handleKeyDown = e => { ... }
<div onKeyDown={isEnter(handleKeyDown)} />

Or you could simplify the ubiquitous need to get event.target.value with something like:

const targVal = fn => ({ target: { value } }) => {
return fn(value)
}
const handleInputChange = value => { ... }<input ... onChange={targVal(handleInputChange)} />

Regardless of the particulars of your needs to use event data or send messages to the event object, the pattern remains the same:

  1. Separate each event intervention into its own higher order function that is curried to receive the event object as its last argument.
  2. In each function, handle the event specific logic for that function (typically related to the name of the function).
  3. a) Return the result of invoking the callback function with the event object. b) For failed predicate examples, return the event object.

With this approach, you can compose event related logic with core business logic in a more decoupled way. This allows core business logic to be simpler and easier to test because it doesn’t need to be all up in the event object’s business.

I hope this adds some declarative shine to your JSX and simplifies your event handlers. Please let me know if this is formally implemented elsewhere and I just missed it in my research. Thanks!

--

--

Mike Schutte
Vue.js Developers

Licensed Driver 🚗 TSA Pre ® AeroPress Barista ☕️ Conversational in Emoji 🤟 Anti-mouse (💻✚🏠) Pro-listening 👂he/him 👨‍💻 @TEDTalks