Hyperapp: Revamp events/emit API.

Created on 4 Jul 2017  路  16Comments  路  Source: jorgebucaran/hyperapp

259 has been merged, #275 is also on the way and we've decoupled the event emitter from app().

Thanks @zaceno and everyone else: @lukejacksonn @selfup @jamen @dodekeract who contributed to the discussion.

As a result, you can now create isolated hyperapps that can communicate to each other or use the emitter to communicate with your hyperapp from other parts of your application.

I think the API now looks wonkier, however, but I just had an idea about how we could utilize #259 to revamp the events API and events function signature.

Code is worth a thousand comments so here is what I am talking about:

const { emit } = app({ state, view, actions, mixins })
  .on("ready", (state, actions, data /*,emit*/) => {
    /*
      We don't need emit as a last argument here anymore.
    */
  })
  .on("render", (state, actions, data) => {})
  .on("action", (state, actions, data) => {})
  .on("update", (state, actions, data) => {})

As for mixins, we can simply pass the emitter to them.

const MyMixin = (app, { emit, on }) => ({
  ...
})

Changes

  • This means we don't need app.events anymore.
  • I've also renamed loaded to ready. This makes sense now that app() is to become sync #275.
Discussion

Most helpful comment

I'm not really a fan of adding .on(...). It seems like an odd addition to me, when everything else is specified through the object. I like how centered that is personally.

Also, inside mixins are you supposed to set emit.on(...) at the base?

const MyMixin = (app, emit) => {
  emit.on('ready', (state, actions) => {
    // ...
  })

  return {
    actions,
    state,
    // ...
  }
}

This doesn't really appeal to me.

All 16 comments

I like this change a lot! Having the emitter available in mixins, I see some potential for new "emulated stateful component" implementations in mixins too :)

A thought: does .on("event"... imply there will be an .off("event"... -- and would that even be necessary. (I think not, because we haven't needed it this far, but the thought struck me)

For #259 and #275 I imagined they'd be more for hooking into certain modules like websockets or SSR, etc.

As a result, you can technically create isolated hyperapps that can communicate to each other or use the emitter to communicate with your hyperapp from other parts of your application.

Is this an okay path to take? I feel like we lose a sense of single-state doing that.

I'd say it's pretty safe. I mean: there is still only one (approved) way of changing the state, and that is by invoking actions. Events in themselves can't change the state, they can only call actions. And since actions are only provided inside event handlers, there's little risk you'll spread out your state-mutations all over the place.

I'm not really a fan of adding .on(...). It seems like an odd addition to me, when everything else is specified through the object. I like how centered that is personally.

Also, inside mixins are you supposed to set emit.on(...) at the base?

const MyMixin = (app, emit) => {
  emit.on('ready', (state, actions) => {
    // ...
  })

  return {
    actions,
    state,
    // ...
  }
}

This doesn't really appeal to me.

@jamen Also, inside mixins are you supposed to set emit.on(...) at the base?

If you are using events, yes.

@jamen I'm not really a fan of adding .on(...)

Me neither, but emit exists. Where there is an emit, there's also an on. I wish we could figure how to do all this without either, but that seems unlikely.

It doesn't really bother me that there is no .on(...), because app.events operates as the replacement.

Maybe "dispatch" or "send"? Same meaning just different vibe.

Edit; But then what the hell would you call the receiving end :thinking:

There is another issue which is I want to simplify the things we use the most at the expense of those parts we don't.

I've always found this more complicated than it needs to be.

app({
  ...,
  events: {
    loaded: () => console.log("Hello")
  }
})

And I rarely use any of the other events.

app({ ... }).on("loaded", () => console.log("Hello"))

Looking at it again, I think maybe I'm with @jamen... I haven't really missed the "on" before. I mean, I haven't had the need to be able to "spread out" my event handlers outside the app definition.

What I do like is that since app returns emit, and if we start passing emit to mixins, we won't need to pass "emit" along to all the actions (and it would make emit available in views without passing the parameter -- very nice).

But we could still do that, without replacing app.events with .on()

Maybe props.on?

app({
  on: {
    ready: () => console.log("Hello")
  }
})

If only there was something like:

app({
  on.ready: () => console.log("Hello")
})

:thinking:

@zaceno I just realized you could've always accessed emit without having app return it.

var emit
app({
  events: {
    loaded: (s,a,d,e) => { emit = e ; ...}
  }
})

Definitely not pretty at all, but it was not impossible.

@jbucaran ah. Hadn't thought of that, but you're right of course. Still think returning emit is more elegant (and if we're going to return something, I'd say it's the most natural thing) But that's good to know if we ever need to revert the return-emit PR

@jamen @zaceno What about renaming events to on?

Didn't realize this before either, but events are a possible way to hack "getters" into hyperapp without causing a rerender (and potential infinite loop). I think this should be possible (although I'm sure it's an implementation detail not to be relied on):

var emit = app({
  ...
  view: (state, actions) => <div>
    Calculated val: {emit('getCalcedVal')}
  </div>
  events: {
    ...
    getCalcedVal: (state) => (state.x + state.y / state.z)
}
})

I personally like the current declarative way to listen to events. .on() is imperative and also involves that you will eventually .off later, which is rarely the case at the app level.

A rule of thumb would be:

  • app lifetime things: declarative.
  • others: imperative.

IMO both API should be available, so you can listen and forget using the declarative syntax, or handle more complex things using the on()/off() brothers.

Thanks @ngryman!

I received a lot of great feedback here.

Closing in favor of https://github.com/hyperapp/hyperapp/issues/278.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dmitrykurmanov picture dmitrykurmanov  路  4Comments

ghost picture ghost  路  3Comments

joshuahiggins picture joshuahiggins  路  4Comments

jbrodriguez picture jbrodriguez  路  4Comments

VictorWinberg picture VictorWinberg  路  3Comments