Reactive Frontend Patterns

User interfaces are tree structures, with nested components. Describing this is easy, but describing reactive change for highly interactive frontends is hard. 5 September 2021

# A tree of boxes

User interfaces are best represented as a hierarchy of boxes; big boxes that contain one or more smaller boxes, which themselves contain even smaller boxes, all the way down until the smallest boxes are individual glyphs on screen. For this reason, the DOM and its serialised version, the HTML are naturally hierarchical.

But UIs are not static, they have to change in response to user actions and other external events. In this article, I talk about some of the patterns and mental models used to create highly interactive web UIs.

# Direct change

In the early days of the web, jQuery  heralded in a world of interactive UIs. Its programming model was simple: when some event happens, select a handful of nodes that needed to respond to the event, and update only those nodes. It was unopinionated about how the HTML was generated, and directly modified the DOM with minimal overheads.

$( "#my-cta-button" ).on( "click", function( event ) {
$( "#my-target-message" ).show();
});

However, it didn't scale very well with ever-growing complexity. It is a difficult mental model to reason about change; developers have to think about the UI tree before and after a change. And when there are many changes that can happen in different and arbitrary order, the complexity increases exponentially.

Recognising that related changes are often grouped, Stimulus  introduces the concept of controllers and make it easy to target related nodes and the change propagation among them. Like jQuery , the DOM is directly manipulated, and changes to the DOM due to other sources (3rd party libraries, for instance) are also observed and reacted to accordingly. Despite being very capable, it was rather late to the scene, and other mental models have taken root…

# Indirect change

In the past decade or so, a different paradigm led by React , Angular , and Vue  has been hugely successful. These offer an indirect approach, whereby the developer's mental model is focussed on the desired tree of components, and the framework automatically figures out how to achieve the necessary change from one tree to the next.

Firstly, an intermediate representation of the DOM, known as the virtual DOM is calculated. The intermediate representation acts as a staging area, whereby the necessary changes can be calculated in a second step. At opportune moments (avoiding blocking the main thread), these changes are flushed to the real DOM. The batching itself is necessary, because changes to the real DOM can trigger expensive reflows. Thus, the virtual DOM is actually pure overhead  that exists only as an implementation specific detail.

To optimise performance, only sub-trees that need to be recalculated are. Subscription based state management libraries like MobX  can offer substantial performance improvements in React applications, by triggering only relevant re-renders. Judicious use of memoisation can help too, to selectively disabling parts of the virtual DOM that don't need to change. Both these leak the underlying frameworks implementation details, and burdens the developer.

Even more sophisticated solutions like SolidJS  directly subscribe the parts of the component and sub-components to the variable of interest. Thus, instead of re-rendering the entire sub-tree, it only re-renders the actual parts that changed. Fast by default, and no virtual DOM needed. The developer mental model is based on a hierarchy of components, but the execution model is a lot closer to changing the DOM nodes directly.

# Automatic change

Where the above methods rely on the Javascript runtime to determine the changes required to modify the DOM. Further improvements can be made by deducing the dependencies at build time using static code analysis. Frameworks like Svelte  and Marko  achieve near-native performance this way. And because the subscription model is determined at build time, only the minimal amount of Javascript is needed in the client. Indeed for static components like headers and footers, the code needed can simply be the final HTML itself!

# Summary

Web UIs are inherently hierarchical. Early efforts at interactive UIs were focussed on enabling developers to think about the change needed. This proved to scale poorly with complexity, and so a shift towards frameworks that detect the required change automatically. The most common way currently is through run-time Javascript solutions, but there is increasingly more competition in the compiled code solution, where static analysis allows the creation of really small and highly efficient Javascript bundles.