Modular Hyperapp - Part 5
Published: 2020-08-18 14:10:49

> ##### _NOTE: this article series is superceded by [this article](https://dev.to/zaceno/a-fresh-take-on-modular-hyperapp-4njm)_

In part 4 we defined a module that encapsulated the actions and view of a counter. It looked like this.

```js
import {h, text} from 'https://unpkg.com/hyperapp'

const init = x => x

const increment = x => x + 1
const decrement = x => x - 1

const model = ({getter, setter}) => {

    const Increment = state =>
         setter(state, increment(getter(state)))

    const Decrement = state =>
         setter(state, decrement(getter(state)))

    return state => ({
        value: getter(state),
        Increment,
        Decrement,
    })
}

const view = model => h('span', {class: 'counter'}, [
    h('button', {onclick: model.Decrement}, [ text('-') ]),
    text(model.value),
    h('button', {onclick: model.Increment}, [ text('+') ]),
])

export {init, model, view}
```

We saw how an app can integrate this module with just three easy steps:

- `init` the counter state in some slot in the app state.
- get the `model` function for a counter instance, by providing the `getter` and `setter` for accessing the state.
- put the counter view somewhere in the main view, passing it `model(currentState)` as an argument.

The pattern is easy, but still practically useless, since a counter can't affect the app and vice versa. It would just sit there being a counter with no purpose (unless you happen to find counters amusing).

### Useful App Modules

In this article we'll expand on the pattern to make _useful_ app modules. We'll keep using the counter as an example, but we need to make it a bit more advanced first. We'll need it to respect a maximum and minimum value:

```js
//...

const increment = (x, max) => Math.min(max, x + 1)
const decrement = (x, min) => Math.max(min, x - 1)

const model = ({getter, setter, min, max}) => {

    const Increment = state =>
         setter(state, increment(getter(state), max))

    const Decrement = state =>
         setter(state, decrement(getter(state), min))

    //...
})

//...
  
```

Alright! Now: how can we make this module interact with the rest of the app? 

### Writing

The module already encapsulates its own views and actions for _users_ to increment or decrement the counter. But could we allow the _app_ to increment/decrement programatically, from some other action? Yes, we just need to provide functions for that. Here's what I suggest:


```js
//...

//the function formerly known as model:
const wire = ({getter, setter, min, max}) => {

    const _increment = state =>
        setter(state, increment(getter(state), max))

    const _decrement = state =>
         setter(state, decrement(getter(state), min))

    const Increment = state => _increment(state)
    const Decrement = state => _decrement(state)

    return {
        increment: _increment,
        decrement: _decrement,
        model: state => ({
            value: getter(state),
            Increment,
            Decrement,
        })
    }
}
//...
export {init, wire, view}
```

I changed the name of `model` to `wire`. Instead of returning a function, it returns an object containing the original function, now named `model`. That's just moving things around and not important. The big news is the `increment` and `decrement` functions now also returned from `wire`.

`increment` and `decrement` are like primitive transforms, but defined using `getter` and `setter` so they operate on the _full app state_. I call this kind of functions "mapped transforms".

Because mapped transforms operate on the full app state, they can be called from within any action anywhere in the app, without violating the principle of loose coupling.

### Reading

Another module might want to access the value of a counter. It's already possible by calling `foo.model(state).value`, but if we like we could also add a more direct way:

```js
//...
const wire = ({getter, setter}) => {

    const _value = state => getter(state)

    //...

    return {
        value: _value,
        increment: _increment,
        decrement: _decrement,
        model: state => ({
            value: _value(state),
            Increment,
            Decrement,
        })
    }
}
//...
```

That way another action could call `foo.value(state)` to get the value, rather than `foo.model(state).value`.

### Reacting

Finally, an app using our module might need to _react_ to something that happen in our module. In this particular case, we want let the app to know when a user incremented or decremented the value. 

By requiring the app to pass an `onIncrement` and an `onDecrement` function alongside `getter` and `setter`, it tells the module what to do in those events.

```js
//...
const wire = ({
    getter,
    setter,
    min,
    max,
    onIncrement,
    onDecrement
}) => {

    //...

    const Increment = state => {
        let old = _value(state)
        state = _increment(state)
        if (_value(state) === old) return state
        else return onIncrement(state)
    }

    const Decrement = state => {
        let old = _value(state)
        state = _increment(state)
        if(_value(state) === old) return state
        else return onDecrement(state)
    }

    //...
}
//...
```

We only need to call `onIncrement` when a _user_ clicked the "+" button (not for calls to `increment` by other actions). And we only call it if the value actually changed. If it was already at the max then no increment really happened so we leave it alone.

### Wiring Modules Together

Now that our module has inputs and outputs, let's wire together an app with it!

Say we are making an app for creating character-sheets for a role-playing game (the old-school kind with dice, pencil & paper, et.c.) A character can have between one and five points for each of the four attributes: strength (STR), dexterity (DEX), intelligence (INT) and charisma (CHA). You have 12 points to spend on your character attributes however you see fit.

We'll use counters for the spending/unspending of points. That means we'll hook the counters together so that we never spend more than 12 points total.

Here's one way we could do it:


```js
import * as counter from './counter.js'

const init = () => ({
    points: 8,
    strength: counter.init(1),
    dexterity: counter.init(1),
    intelligence: counter.init(1),
    charisma: counter.init(1),
})

const spendPoint = (state, orElse) =>
    !state.points
    ? orElse(state)
    : {
        ...state,
        points: state.points - 1,
    }

const returnPoint = state => ({
    ...state,
    points: state.points + 1
})

const strength = counter.wire({
    getter: state => state.strength,
    setter: (state, strength) => ({...state, strength}),
    onIncrement: state =>
        spendPoint(state, strength.decrement),
    onDecrement: returnPoint,
    min: 1,
    max: 5,
})

//... ditto for dexterity, intelligence & charisma
```

And just to prove it works:

{% codepen https://codepen.io/zaceno/pen/MWyyZNB %}

> Of course you could reduce repetition in those counter-wirings with just a little more effort. I opted to leave as is to keep it more clear.

### Closing Remarks, Part 5

Our introduction of "mapped transforms" seem close to calling methods on objects in OOP. And our talk about "reacting to events" in modules might lead you to think of emitting and listening for events.

I used that language because it fits the mental model of interacting modules. But what is _really_ going on?

- When the user clicks the "+" button in the STR-column, then if the strength points were already 5, nothing happens.

- If strength was just 2 it would go up to 3, and `onIncrement` is called.

- `onIncrement`, is a reference to the mapped transform `spendPoint`.

- If there happen not to be any more points to spend, the `orElse` function is called.

- `orElse` refers to the mapped transform `strength.decrement`. That brings the strength value back down to 2. 

All of those functions were defined in separate places for the sake of modularity. But they are actually executed as a _sequence of state transformations_ entirely within the `Increment` action of the strength counter.

Before we can wrap up (in part 7) we should take a look at how subscriptions and effects play in to things. That will be the topic of part 6.