Use Lenses in Hyperapp

You can shorted your state mutations by using functional lenses.

If you look at Hyperapp examples or even simplest counter example, you will see how it deals with state updates. Here is a typical example - incrementing and decrementing a counter.

Hyperapp works inside script type="module"

Hyperapp is nice - it passes the complete state object to every action function, allowing every action to be pure if it wants to be. But the state object is the single source of truth, which means that sometimes actions have to work with properties deep inside the object. Even the above example - there is a lot of boilerplate in dealing with the count property.

1
2
3
4
const actions = {
down: () => state => ({ count: state.count - 1 }),
up: () => state => ({ count: state.count + 1 })
}

Every action has to get the desired property, compute its new value and return the new state object (or at least changed key). Here is how we can simplify this code using functional lenses implemented in Ramda library.

First, notice that up action for example always works with property count of an object passed to it. We can create a lens that will "look" at the property count using R.lensProp function.

1
2
import {lensProp} from 'ramda'
const countLens = lensProp('count')

If we have a lens, and we know how we want to change the value, we can create new function that will be ready to increment the count property.

1
2
import {lensProp, over} from 'ramda'
const incrementCount = over(lensProp('count'), x => x + 1)

Of course, Ramda comes with increment function R.inc, so we don't have to write one ourselves

1
2
import {lensProp, over, inc} from 'ramda'
const incrementCount = over(lensProp('count'), inc)

Function incrementCount is waiting for an object to be passed in. That object should have count property, and it better be a number!

1
2
const incrementCount = over(lensProp('count'), inc)
incrementCount({ count: 10 }) // {count: 11}

Nice! We can create a "decrement count" function similarly. We can even reuse the same over(lensProp('count')) functions, thanks to all Ramda methods being curried. My final code looks tight and reads almost English-like.

1
2
3
4
5
6
import { lensProp, over, inc, dec } from 'ramda'
const count = over(lensProp('count'))
export const actions = {
down: () => count(dec),
up: () => count(inc)
}

And that's how you can clean up actions mutating state in Hyperapp. You can find the change to my counter example in commit bahmutov/hyperapp-counter-jsx-example/commit/0e2915

Related blog posts