mithril.js icon indicating copy to clipboard operation
mithril.js copied to clipboard

Hooks timing

Open pygy opened this issue 7 years ago • 4 comments

Hi all, here follows some brain storming about the order in which the hooks fire (this may be a dupe, because some of these ideas have been brewing for a while... Kudos to @nraibaud for a previous discussion on the topic).

Currently, the following statements are true:

For a single component, the following sequences run:

  • when created:

    • oninit
    • view
    • the DOM node is appended
      • (oninit and view for children, and corresponding DOM additions)
    • oncreate (first for the current nodes then its children)
  • when updated:

    • onbeforeupdate
    • view
    • the DOM is mutated
      • (onbeforeupdate and view for children, and corresponding DOM mutations)
    • onupdate (first for the current nodes then its children)
  • when detached:

    • onbeforeremove
    • onremove, first for the node then its children.
    • the DOM node is detached.

However, nothing guarantees that the hooks of a list of nodes will fire in source order. Actually, for keyed diff, the relative order is all over the place.

In other words, the hooks all fire on the descending edge of a depth-first traversal, with no order specified in a given nodes list. In a keyed list, going from ABC to ADC, B.onbeforeremove could fire before or after D.oninit (I know it sometimes happens after).

It may be nice to

  1. have the hooks fire in source order, even in keyed mode,
  2. have the post-view hooks fire on the ascending edge of the traversal (oncreate / onupdate firing on the children before they do on the parent).
  3. guarantee that B.onbeforeremove fire before D.oncreate. Not sure where a synchornous B.onremove would go relative to D.oninit, D.view and the D DOM creation...

Some of that will probably come at a cost, perf-wise and/or size-wise.

pygy avatar Apr 14 '18 14:04 pygy

would be good to show what specific userland problem this solves (or link to prior discussion for context)

leeoniya avatar Apr 14 '18 14:04 leeoniya

For 1) I've had a case where I needed to put the focus on the first invalid field in a list. It was thankfully not a keyed list, ensuring that the nodes were traversed in order, so I could just use { invalidField && ...{onupdate: focus.setIfNeeded} } in the attrs. focus was a stateful helper like this:

  const focus = {
    needed: <boolean> false,
    setIfNeeded: function setIfNeeded({dom}: VdomHTML<any, any>) {
      if (focus.needed) {
        setTimeout(() => dom.focus(), 100) // that timeout isn't needed anymore
        focus.needed = false
      }
    }
  }

Had it been a keyed list I would have had to make two passes to determine where to put the focus.

For 2) you may want to get the dimensions of an element whose children mess with the DOM directly. So the post-view hooks must fire after the ones of the children (and one must sync the computed properties first, off course). I don't have a scenario where the opposite order would be useful.

For 3) One may expect the old node to be removed before the new one is created, that was my expectation at some early point, at least. I don't remember why I wanted to rely on it though.

pygy avatar Apr 14 '18 15:04 pygy

For my part i did a hook named phase. This can be registred on "up" or "down" phase.

nraibaud avatar Apr 14 '18 18:04 nraibaud

@pygy So what's the status on this in light of #2156?

dead-claudia avatar Nov 14 '18 00:11 dead-claudia