DevLog.at

Barney's Devlogs

Working on user interfaces, bogged down in the vagaries of expressive Javascript

ProfileFollowing

A mate of mine is a writer by trade but around 2-3 years ago started mucking about with front-end web tech - HTML, CSS, Javascript. He's just about finished making his portfolio website and asked for a hand testing / reviewing from a technical angle and sorting out some finer points. It is such a joy seeing a consumately designed indie website. I'm looking at 4000 lines of hand-written CSS and it really takes me down memory lane.

2m

Working on a low urgency little job - a simple CRUD - which offers an opportunity to experiment with UI flourishes, and muse on desirable patterns for hyperscript expression. Since React Hooks arrived, the mind-share leader has finally ditched the execrable convolution of its OOP-centric class-based component API and created a low-friction single-scope alternative that makes the whole UI tree far more expressive. It's drastically easier for React users to conceive of recursive trees of components, any of which can contain relatively straightforward imperative code dealing with state, setup, teardown, side-effects, DOM manipulation etc... All of which can in turn be easily factored out into further component expressions.

In practice this means React is, shockingly, more expressive for many use cases than my beloved Mithril.

And in turn, this ushers in a significant milestone in the maturity of hyperscript components as a fundamental idiom: underneath all the window dressing (JSX, hooks, streams), what's really being enable here is a new baseline of common sense as to what level of expressive power can be internalised as recursive, composable UI entities. And it's this kind of idiom I'm pondering on: what next for Mithril?

2m

React's Hooks and their more prosaic predecessor, Mithril's Closure Components, are ultimately predicated on moving on from the received wisdom of the Object Oriented Programming school - which is generally internalised as 'every problem can be solved by a given entity's configuration via a set of properties' - and handing over a significant proportion of the problem domain to good old imperative code for persistent references, and function expressions - via a set of functors - for declarations of behaviour & structure.

When it comes to Mithril, the library-managed behaviours (lifecycle) have a bit more power than React traditionally allowed because they can be declared inline at the drop of a hat, much like standard DOM behaviour (event handlers). (In React, these can only be defined in component definitions). So some situations remain contrived:

const MithrilComponent = {
  view: () =>
    m('.foo', {
      oncreate: ({dom}) =>
        dom.animate(...animationFoo),
    },
      m('.bar', {
        oncreate: ({dom}) => 
          dom.animate(...animationBar),
      }),
    ),
}

// vs

function ReactComponent(){
  const [foo, bar] = [useRef(), useRef()]

  useEffect(() => {
    foo.current.animate(...animationFoo)
    bar.current.animate(...animationBar)
  }, [])

  return (
    <div class='foo' ref={foo}>
      <div class='bar' ref={bar}>
      </div>
    </div>
  )
}

By some contentious measure, this is a case where React remains in the murky old world of declaring references in one place, assigning them in a second, and acting on them in a third 😬. By contrast, whereas we might describe Mithril as more amenable to forthright DOM access and plain Javascript idioms, React Hooks introduce a bit more surface complexity but then leapfrog Mithril when it comes to composing state with reactivity and lifecycle. For all the seemingly redundant surface in the previous example, the case for intersecting concerns is simpler and notionally more efficient in React (the worst-case scenario for Mithril is a component which requires one-time setup with access to DOM and the latest component input).

The best of both worlds, I feel, lies in an idiom that strives to avoid both imperative & property-based code in favour of drop-in expressions. Having popularised render props, React has for some time normalised the idiom of reusable component which provide data to their subtree; with composeable hooks - which are in turn the internals of components, and can therefore be exposed as components consuming input and, via render props, expose output to a subtree - without breaking out of declarative JSX expressions.


However, I don't believe this will come to pass too soon in React. Hooks' surface API is exotic enough, what with their return values practically mandating destructuring for readable usage & their stream-ish, reducer-ish variadic signatures. The solution, for React at least, is patently in the distinction of custom hooks for behaviour - which can be composed to the nth degree without touching the traditional 'virtual DOM' surface - and JSX for DOM. Anything that muddies that distinction is likely to make users balk at the obfuscation between two very esoteric API surfaces.

So I predict that idiomatic React UI architecture will continue to consist of singular component units responsible for appearance, structure, & behaviour.

2m