DEV Community

TJ Kandala
TJ Kandala

Posted on

Baahu: the state machine-based SPA framework

Baahu

What is Baahu?

Baahu is a zero-dependency Moore machine-based SPA framework for Javascript + TypeScript.

Why?

Before creating Baahu, my UI library/framework of choice was React. The introduction of hooks was controversial, but in my opinion, hooks have turned React into the library it always claimed to be. Baahu was not created due to my dissatisfaction with the direction of React itself; I am excited to see the ideas of the React team come to fruition.

After learning about state machine-based UI patterns, I was excited to structure my React applications around state-machines. Creating complex components with React + XState was an absolute joy.

However, state machine-based architecture proved to be infeasible in React due to the overlapping constraints of immutability and state machines, resulting piles of abstraction, and performance overhead.

Overlapping Constraints

Languages and libraries often enforce constraints in order to take advantage of the properties gained from the power lost.

React leverages the constraints of immutability to make programs easier to reason about, as well as to easily implement some performance optimizations (e.g. if the old state is referentially equal to the new state, don't rerender). XState leverages the constraints of state machines/statecharts to make it impossible to reach invalid states.

However, if you enforce too many constraints, you lose too much power. This effect is worse when the constraints come from two competing + overlapping abstractions. For developers, this can reduce productivity due to the amount of "moving parts" you have to keep track of. For the end user, the consequence is a worse user experience (performance) due to the amount of code they have to download and run on each event.

State machines are inherently stateful. In the React community, we implement immutable state machine libraries, such as XState or Robot, in order to use the state machine abstraction while adhering to React's constraint of immutability; React won't render if you don't create a new state machine object.

There are other benefits to "stateless" state machines, such as easily recording the history of states, but the constraint of immutability adds little value when you consider that state machines are already sufficiently constrained by their public API.

A stateful state machine has essentially the same desirable properties as a stateless state machine. You send the state machine an event/message, and it updates based on its current state + specified transition

The idea behind Baahu was to cut out the middle-man of React + immutability to simplify the mental mental of state machine-based UIs, and to improve performance by removing layers of abstraction.

Abstraction^2

React + XState mental model

The impedance mismatch between the change-driven view layer and the event-driven state layer results in an opaque mental model.

From your browser event handler, you send an event to the interpreted machine service. The service handles the event, creating a new state object. The service hook calls setState, informing React that the component using this machine has updated. React checks to see if the new state is referentially equal to the old state. If not, the component will rerender. Don't forget, rendering isn't a synchronous task.

Having one XState island in your app is not too much to handle. However, the mental model grows when you want multiple machine components that communicate with each other.

The community solution is to use context, observables, or event buses. To solve this problem, I created a small reactive library (originally based on RxJS) to push state changes between distant machine-based components. It worked well for me, but it was at this point that I became uncomfortable with my bundle size and the height of my call stacks.

According to the creator of XState, "at a certain point, you're fighting the framework over control of the state, since the framework wants to be control of more state. It's possible to use XState in a completely framework-agnostic way, but that may require doing things that aren't that idiomatic in that framework unfortunately."

I wanted a way make every component an explicit state machine without the overhead, and without going against the "happy path" of the UI framework.

Baahu mental model

From your browser event handler, emit a Baahu event. Baahu will transition the machines that listen to this event, and only rerender those machines.

Improving Performance

In React, you usually don't need external state management libraries. To share state between components, simply "lift state up" to the lowest common ancestor. Sadly, this top-down "props model" of state management leads to some unfortunate performance characteristics.

View full image

react rerendering

This is not a huge problem for smaller subtrees. After all, creating objects is much cheaper than reading from or writing to the DOM.

When applications grow, bigger and bigger subtrees rerender in their entirety on state changes. This can lead to extremely slow inputs. Modern computers already have more input latency in the terminal than computers from the 1970s, so we should be careful not to make web applications inaccessibly slow.

In Baahu, only the components that listened to an event rerender. Read how Baahu performs minimal renders here and here

Who should use Baahu?

Baahu is most suitable for medium-sized applications with medium-to-high levels of interactivity.

React seems to have big plans for problems in the "extremely large SPA" space, but Baahu doesn't plan to compete here. Baahu is good for apps with deep and/or broad virtual DOM trees (e.g. Netflix). If your app has tons and tons of routes (e.g. Facebook), and your biggest problems include managing your sheer quantity of components, React is for you.

Baahu is only 4.3kb gzipped, and it comes with built-in routing, state-management, and code-splitting. It also outperforms major frameworks (Svelte, Preact, Vue, React, Angular) in benchmarks.

In short: If you want a small and fast batteries-included framework that takes advantage of the constraints of state-machines, you should consider Baahu.

Top comments (1)

Collapse
 
mrjjwright profile image
John Wright • Edited

Really clean great reasoning and creation of a framework.

How do finite state machine driven frameworks like Baahu and Xstate conceptualize derived state? One of the principles of Mobx, Vue, SolidJS etc is minimal state, derive, derive, derive. These derivations are written in plain (or sometimes memoized or computed) functions that are called from the bottom of the tree reaction functions to state changes, such as React Mobx observer renders or SolidJS render effects.

If too much state is bad, how does a FSM driven approach keep top level state minimal and derived state functional? Not doubting, I have wondered about this.