Humble Chronicles: State Administration @ tonsky.me

Not too long ago I’ve been attempting to enhance state administration and part API in Humble UI. For that, I’ve tried to learn and compile all of the potential recognized approaches and synthesize one thing from them.
I haven’t selected something for Humble UI but, let’s say I’m in an experimenting section. However I believe my notes might be helpful to shortly get a birds-eye overview of the sphere.
This can be a compilation of my analysis up to now.
Traditional UIs like Swing, unique native Home windows/macOS APIs and the browser’s DOM are all applied in an OOP method: parts are objects they usually have strategies: addChild
, removeChild
, render
. That matches so nicely, really, that you just would possibly assume OOP was invented particularly for graphical UIs.
The issue with that strategy is code duplication: you need to outline each how your preliminary UI is constructed and the way it will change sooner or later. Additionally, quantity of transitions scales as N² in comparison with the quantity of states, so in some unspecified time in the future you both get overwhelmed or miss one thing.
However, OOUIs present nice efficiency, a really easy programming mannequin and are broadly used all over the place.

Given the above, it’s solely pure that Humble UI began with the OOP paradigm. Sure, we’ve stateful widgets and part situations, not capabilities or courses.
Look, mutable fields and inheritance! In Clojure!
(defparent AWrapper [child ^:mut child-rect]
protocols/IComponent
(-measure [this ctx cs]
(when-some [ctx' (protocols/-context this ctx)]
(core/measure little one ctx' cs))))
(core/deftype+ Clip []
:extends core/AWrapper
protocols/IComponent
(-draw [_ ctx rect ^Canvas canvas]
(canvas/with-canvas canvas
(canvas/clip-rect canvas rect)
(core/draw little one ctx rect canvas))))
These objects can lay out, draw themselves and deal with occasions, however in true Clojure vogue, they will’t be modified.
I imply, theoretically, positive, I might add one thing like setChildren
and the like, however what’s the purpose if we aren’t getting into that route anyway?
However wait! — you’d say. I’ve actually seen Humble UI apps modifying themselves!
And also you’ll be proper. There’s a particular kind of part, dynamic
, that doesn’t have a set checklist of kids. As a substitute, it takes a perform that generates and caches them primarily based on its inputs. When inputs change, previous kids are thrown away and new ones are generated.
On this instance, when *clicks
modifications, a brand new label shall be created and the previous one shall be thrown away.
(def *clicks
(atom 0))
(def app
(ui/dynamic _ [clicks @*clicks]
(ui/label (str "Clicks: " clicks))))
You may get fairly far with that strategy. Nevertheless, not far sufficient. The issue with dynamic is that it throws away the whole subtree, irrespective of which elements have modified. Think about
(ui/dynamic _ [p @*p]
(ui/padding p
(ui/rect (paint/fill 0xFFF3F3F3)
(ui/label "Label"))))
On this instance, solely ui/padding
part must be re-created, however its kids could be thrown away and re-created, too. Typically it may be mounted, really, by writing it this manner:
(let [body (ui/rect (paint/fill 0xFFF3F3F3)
(ui/label "Label"))]
(ui/dynamic _ [padding @*padding]
(ui/padding padding
physique)))
Bear in mind — parts are values, so the physique
reference will keep the identical and shall be captured by dynamic.
That is each good and dangerous: it really works, nevertheless it’s kinda backward (you wouldn’t need to write your UI this manner).
It additionally creates very complicated “proudly owning” semantics. Like, who ought to “unmount” the physique
in that case? Ought to it’s padding
? Or dynamic
? Truly, it may be neither of them, as a result of physique
outlives them each, they usually don’t have any approach of understanding this.
Why is it an issue? Stateful parts. If I throw away and re-create the textual content discipline, for instance, it’ll lose choice, cursor place, scroll place, and so on. Not good.
Funnily sufficient, present implementation of textual content discipline asks you to carry its state as a result of it has nowhere to place it reliably.
However general, we managed to get fairly far with this strategy and polish some stateful parts, so I don’t take into account it a waste.
So in some unspecified time in the future, programmers determined: we’ve had sufficient. Sufficient with OOUI, we don’t need to write
window.add(button);
window.present();
anymore. We would like consolation!
And that’s how declarative UIs have been born. The thought is that you just describe your UI in some easier language, bind it to your knowledge, after which the pc goes brrr and shows it someway.
Nicely, why not? Sounds good, proper?
That is what folks have give you.
Templates
You describe your UI in XML/PHP/HTML/what have you ever, sprinkle particular directions on high, give it to a black field and it magically works!
Instance: Svelte
<script>
let depend = 1;
</script>
<button on:click on={handleClick}>
Rely: {depend}
</button>
The upsides of this strategy are that language might be quite simple and really declarative, and in addition that you are able to do quite a lot of optimizations/preprocessing earlier than turning it into UI. Most declarativity!
The downsides are, in fact, that you need to work in a second, “not actual” language, which is often much less highly effective, tougher to interop with, and fewer dynamic.
In all probability due to the constraints of templates, the MVVM sample was born: you put together your knowledge in an actual language and get it right into a form that may be consumed by a easy template.
Procedural DSLs
You name builder capabilities in the proper context and your UI framework someway tracks them and turns them into parts. Examples could be Expensive Imgui or Jetpack Compose:
ImGui::Textual content("Hiya, world %d", 123);
if (ImGui::Button("Save"))
MySaveFunction();
ImGui::InputText("string", buf, IM_ARRAYSIZE(buf));
ImGui::SliderFloat("float", &f, 0.0f, 1.0f);
Discover that you just don’t explicitly “add” or “return” parts anyplace. As a substitute, simply calling builders is sufficient. Nearly procedural type 🙂
The upside: the code may be very compact.
The draw back: you may’t work with parts as values. Like, can’t put them in an array and reverse it, or take the primary 10, or one thing like that. Your builders turn out to be lambdas, and lambdas are opaque: you may’t do a lot about them besides name. Name websites begin to matter, the place they usually don’t: what seems like a standard program has a number of non-obvious gotchas.
Worth-oriented DSLs
In value-oriented DSLs, you come back values out of your parts. Like in React:
export default perform Button() {
return (
<button>I do not do something</button>
);
}
Discover that you just return the button, not name some constructor that provides it to the shape. The way you get — doesn’t matter. The one factor that issues is the precise worth you come back. You are able to do what you need with it: use, ignore, evaluate, use twice, cache, throw away.
That is additionally essentially the most pure method to write applications for my part: pure capabilities taking and returning knowledge. It additionally fits Clojure the very best, so we’ll in all probability need one thing like that for Humble UI.
Notice additionally that <button>
syntax, though technically being a DSL, is only a pure comfort. It doesn’t do something good, it’s only a extra pure approach of writing:
react.createElement('button', {})
which returns a button.
Declarative-OO duality
One other fascinating level is that each one declarative UIs work on high of soiled, mutable, old school OOUI. Flutter has RenderObject
, for instance, and browser UIs make the most of and exploit DOM.
Should you, like me, ever puzzled why didn’t browsers implement React natively someway, in the identical method they adopted jQuery APIs. Nicely, the reply is: you form of want DOM. VDOM sounds cool and fancy so long as all of the heavy lifting is finished in actual DOM.
Summing up
Declarative UIs are nice however require a layer of actual mutable widgets beneath. Which means it’s all overhead, and all we are able to do is make it as small as potential.
However we nonetheless need declarativity, if just for developer expertise alone. We need to write extra concise code and we don’t need to write replace logic.
As Raph Levien put it, “business is quick converging on the reactive strategy” (reactive/declarative, no person is aware of what these phrases imply anymore). We’re not going to argue with that.
In declarative frameworks, every part exists in two types: a light-weight, user-generated description of it (React components, Flutter Widgets, SwiftUI Views, let’s name them VDOM) and a “heavy” stateful counterpart (DOM nodes, Flutter RenderObjects, “actual DOM”).
Reconciliation is a course of of reworking the previous into the latter. This offers up two fundamental sources of overhead on high of OOUI:
-
Rubbish. VDOM, whereas light-weight, nonetheless has allocates objects and must be cleaned up after reconciliation.
-
Diffing. We have to determine how a lot has modified. The much less we do it, the sooner our apps shall be.
The best, however in all probability most wasteful, approach is simply to regenerate the whole UI description on every body. That is what Expensive ImGui does, for instance.
The issue is that then you need to reconcile the entire tree, too. And if some elements of your UI take quite a lot of time to generate — sorry to be you, you may’t skip them.
Right here’s a diagram of the total top-down reconciliation:

Optimized top-down reconciliation
React updates are additionally principally top-down, with two essential enhancements.
First, when producing and evaluating new tree, it has a method to be informed “this subtree hasn’t modified, I promise” and it’ll skip reconciliation for it altogether:

The second optimization is whenever you discover a particular part and solely reconcile its sub-trees:

Sadly, these methods are usually not utilized routinely, so a programmer’s work is required. And even then React provides numerous overhead. The invention of React group was, although, that this overhead just isn’t essential/tolerable generally and is a good tradeoff.
In idea, each methods mixed might enable one to replace one particular part and nothing else, providing you with optimum efficiency. Nevertheless it’ll in all probability be slightly bit cumbersome to write down.
Surgical level updates
Why can’t all React updates be surgical, affecting solely the component in query and neither its mother and father nor kids? Nicely, due to knowledge dependencies! Think about this straightforward UI:
What wouldn’t it seem like in React? One thing like this (sure, I requested ChatGPT to write down it):
perform CelsiusInput(props) {
return (
<div>
<enter worth={props.celsius}
onChange={props.onChange} />
Celsius
</div>
);
}
perform FahrenheitOutput(props) {
return (
<div>{props.fahrenheit} Fahrenheit</div>
);
}
perform TemperatureConverter() {
const [celsius, setCelsius] = useState(0);
const handleCelsiusChange =
(e) => setCelsius(e.goal.worth);
return (
<div>
<CelsiusInput
celsius={celsius}
onChange={handleCelsiusChange} /> =
<FahrenheitOutput
fahrenheit={celsius * 9 / 5 + 32} />
</div>
);
}
On this instance, TemperatureConverter
owns the mutable state and each CelsiusInput
and FahrenheitOutput
have a knowledge dependency on it, acquired by means of properties.
Intuitively it looks like CelsiusInput
ought to personal that state, however beacuse it’s utilized in its sibling, it needs to be declared within the dad or mum part. Due to that, not solely CelsiusInput
and FahrenheitOutput
must be re-rendered, however their dad or mum TemperatureConverter
, too.
One other drawback is that, as a result of TemperatureConverter
is written as a single perform, when setCelsius
is known as TemperatureConverter
its total physique shall be re-evaluated. This, for instance, signifies that a brand new occasion of handleCelsiusChange
shall be created, although it’s fully pointless.
These are two issues that frameworks like Svelte and SolidJS tried to resolve: replace as little as potential, solely parts that should be up to date and nothing extra:

UIs generally, what are they doing? They’re computing one thing to show to the consumer and people issues want to alter shortly. And one of many issues that may assist them change shortly is that they don’t change all of sudden, little bits of them change. You go and click on someplace and a few small a part of what you’re seeing modifications, it’s not every little thing in the whole view being reworked all of sudden.
How do they do it? Nicely,
[…] hidden inside of each UI framework is a few form of incrementalization framework as nicely, since you mainly want this incrementalization for efficiency causes all over the place.
What’s the incrementalization framework? Think about you compute a perform as soon as, however then are requested to compute it once more, with barely totally different inputs. Should you retailer some partial computations someplace and may do the computation sooner a second time, if the enter hasn’t modified an excessive amount of, then you’ve an incremental perform.
The way in which Stable/Svelte remedy re-evaluation drawback is by rewriting your perform physique. They attempt to cut up it into remoted items and insert reactive callbacks the place wanted. I’m unsure I’m an enormous fan of implicit rewrites, however the objective appears noble sufficient to pursue.
Now, think about you construct your UI like this: you begin with some knowledge sources, like a counter
sign (identical as a variable, however reactive). Then you definitely derive some computables from it, like squared
, which is, nicely, counter with sq. perform utilized to it. Lastly, UI parts that show counter
and squared
might be additional derived from these indicators. One thing like:
const counter = reactive(0);
const counterLabel = reactive(() => <div>{counter.worth}</div>);
const squared = reactive(() => counter.worth ** 2);
const squaredLabel = reactive(() => <div>{squared.worth}</div>);
const app = reactive(() => {
<div>{counterLabel} * {counterLabel} = {squaredLabel}</div>
});
This creates an acyclic dependency graph like this:

which could be effectively up to date. For instance, if we bump counter
, it would set off updates of squared
and counterLabel
. Then squared
will set off an replace of squaredLabel
. Each counterLabel
and squaredLabel
might be results that replace their corresponding DOM node, however the return worth is identical — they return precisely the identical node their work with, so that they gained’t set off additional updates in any respect: app construction is static and doesn’t should be revisited.
That is the simplified and on the identical time essentially the most perfect case that we need to at the least attempt to strategy in Humble UI.
UI’s don’t exist in isolation, they should work together with bigger program. Elements on a display screen characterize some state (enterprise mannequin), however they typically even have an inside state to maintain observe of (scroll place, choice, and so on).
In OOUI that wasn’t an issue: you simply preserve the state inside parts. Every part is an object, state is simply that object’s fields. As I stated — OOP suits UI very properly. And if the enterprise mannequin modifications, nicely, you go and alter your UI with addNode
/removeNode
/…
Inside state
React launched an fascinating paradigm: your part is a perform (not an object), however you may request a state inside it and the framework will allocate and observe that state for you. Seems to be a bit bizarre, nevertheless it works:
perform Counter() {
const [count, setCount] = useState(0);
setCount(depend + 1);
}
The trick right here is that useState
will return precisely the identical object over a number of Counter()
calls if and provided that Counter
part stays in the identical place within the tree. That’s referred to as positional memoization. Features and light-weight descriptions (components) are all the time generated anew, however positions within the tree are secure and may have state connected to them.
SwiftUI does the identical, nevertheless it seems even weirder:
struct CounterView: View {
@State var depend = 0
let id = UUID()
var physique: some View {
Textual content("ID: (id.uuidString) Rely: (depend)")
}
}
This seems like an object, however you may’t retailer regular properties inside it. On every render, the framework will create new situations of CounterView
and any inside fields shall be misplaced, however! It would fill fields marked with @State
for you every time with the identical object. In different phrases, id
shall be totally different on every render, however depend
shall be precisely the identical.
Anyhow, SwiftUI strategy works too, though I’d argue it’s a bit counter-intuitive.
Exterior state
Exterior state principally defines what UI parts you must assemble: what number of traces are in an inventory, enabled or disabled button, present warning or not.
All state exterior
One strategy is to make all state exterior. For instance, Humble UI’s textual content fields proper now ask you to carry their state in your atom. Enjoyable, however can get very tedious, particularly when you must clear up the state for parts which can be now not seen.
I believe within the early days of ClojureScript frameworks Circle CI (IIRC) frontend’s state might be completely serialized right into a string and re-created on one other machine, down do textual content choice and button hovers. Provided that no person else does this would possibly counsel that it is likely to be an overkill. Cool flex, although.
Single atom
One other strategy is to place all of your state in a single atom and solely go down sub-trees of that atom. That is what Om pioneered, and different ClojureScript frameworks adopted as nicely. This manner you are able to do very low-cost pointer comparisons on arguments, it’ll be so low-cost it is sensible to do it by default.
In fact, it solely works with immutable knowledge, however fortunately, ClojureScript knowledge tends to be immutable already. 10 years in the past. Good occasions.
I’ve three points with this strategy:
-
First, I don’t need to retailer every little thing in a tree. I need to have choices. I need to use a database.
-
Second, I don’t need to be restricted to a single supply of fact. I need an ad-hoc state, too. Like a number of international atoms that management settings and which I can add/examine/take away shortly. Why not?
-
Lastly, pointer comparisons solely work if you happen to go down sub-trees. Should you e.g.
map
orfilter
a set, the ensuing pointer shall be new every time and also you’ll have re-render, breaking the optimization.
State in incremental frameworks
One simplification in incremental in comparison with React’s prop drilling is you could safely go indicators by means of properties, as solely parts that really learn the worth will get re-rendered.
However I’m additionally fascinated by a special angle right here: what if parts themselves (actual ones, heavyweight objects that do structure rendering, not light-weight descriptions) have been the output of incremental capabilities? I imply, they’d keep secure till they should be modified, proper?

That will imply that we are able to preserve the state inside them naturally, and we gained’t want any VDOM in any respect, simply incremental computations.
That is only a idea at this level, however I’m constructing a proof of idea to see the place it goes. Subscribe for updates!
The way in which we write parts is essential: it should be ergonomic.
For instance, in Flutter architectural overview they name this instance trivial:
class MyApp extends StatelessWidget {
const MyApp({tremendous.key});
@override
Widget construct(BuildContext context) {
return MaterialApp(
house: Scaffold(
appBar: AppBar(
title: const Textual content('My House Web page'),
),
physique: Heart(
little one: Builder(
builder: (context) {
return Column(
kids: [
const Text('Hello World'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
print('Click!');
},
child: const Text('A button'),
),
],
);
},
),
),
),
);
}
}
However to my eye, it reads approach tougher than it ought to. Similar however in Clojure + Hiccup:
[MaterialApp
[Scaffold
{:appBar
[AppBar
{:title [Text "My Home Page"]}]}
[Center
[Column
[Text "Hello World"]
[SizedBox {:height 20}]
[ElevatedButton
{:onPresed #(print "Click!")}
[Text "A button"]]]]]]
Now you may lastly see what’s occurring!
One other essential factor is that the best way you write your parts would possibly impose pointless dependencies or different semantics. We don’t need to make customers select between quick and readable, we would like them to have each.
Mum or dad → little one dependencies
Let’s have a look at React instance once more:
perform TemperatureConverter() {
const [celsius, setCelsius] = useState(0);
const handleCelsiusChange =
(e) => setCelsius(e.goal.worth);
return (
<div>
<CelsiusInput
celsius={celsius}
onChange={handleCelsiusChange} /> =
<FahrenheitOutput
fahrenheit={celsius * 9 / 5 + 32} />
</div>
);
}
See the issue? By being plain JavaScript and plain perform, if TemperatureConverter
must be re-evaluated, the one factor to do right here is to name the whole perform. That means, do all of the calculations once more, allocate new objects and new capabilities once more. Then diffing kicks in, attempting to determine which objects are the identical and that are totally different. That’s a bit an excessive amount of work than obligatory, however React design forces us to do it.
Let’s have a look at one other instance, in Humble UI this time:
(ui/vscrollbar
(ui/column
(ui/label @*depend)
(ui/button #(swap! *depend)
"Increment")))
By the best way it’s constructed, column
will depend on each label
and button
, and scrollbar
will depend on column
.
This parent-children dependency comes naturally from analysis order, however do we actually need it? For instance, if *depend
modifications, we do desire a new label to be created, or an previous one to alter its textual content? Ideally, we might additionally wish to preserve column
, button
and scroll
—they may have essential interior state, e.g. scroll place.
Baby → dad or mum dependencies
One other instance (pseudo-code):
(defcomp little one []
(ui/dynamic ctx [{:keys [accent-color]} ctx]
(ui/fill accent-color
(ui/label "Hiya"))))
(defcomp dad or mum []
(ui/with-context [:accent-color 0x1EA0F2]
(ui/heart
(little one))))
(dad or mum)
I suppose the purpose right here is that dad or mum creates a baby (and will do it conditionally, in a loop, or in another non-trivial approach), so it form of will depend on the kid (at the least it defines it). However on the identical time, the kid it creates will depend on a property outlined by a dad or mum, too.
Once more, possibly it’s not an issue if every part could be a macro that evaluates its kids individually from itself, however nonetheless difficult to consider.
Consider it this manner: some knowledge flows from high to backside, defining which parts ought to be created. Then modifications movement from leaves again to high. Typically change in a sign would possibly invalidate each a leaf and its dad or mum, and that dad or mum would possibly determine to not re-create stated leaf! This might result in some pointless evaluations if not dealt with correctly.
Element persistence
Yet one more instance:
(ui/dynamic _ [has-errors? (boolean @*errors)]
(if has-errors?
(ui/border 0xFF0000
(ui/text-field @*state))
(ui/border 0xCCCCCC
(ui/text-field @*state))))
Ought to these two textual content fields be totally different objects or the identical? I imply, in our instance we would like them to be the identical, however by building, they’re totally different object situations.
In React they would be the identical, as a result of React solely cares about what you come back and reconciles values, ignoring e.g. positional data or object situations.
However then, we are able to trick React, too, to re-create textual content discipline:
(ui/dynamic _ [has-errors? (boolean @*errors)]
(if has-errors?
(ui/border 0xFF0000
(ui/text-field @*state))
(ui/text-field @*state)))
Now, as a result of the return construction is totally different, React will drop the earlier occasion of the textual content discipline and create a brand new one, even when we don’t need it. It’ll drop the state, too. If I perceive this accurately, even keys wouldn’t assist us on this case (Flutter appears to have GlobalKey
for circumstances like this, although).
When working on heavy-weight parts immediately, we are able to do that transformation:
(let [text-field (ui/text-field @*state)]
(ui/dynamic _ [has-errors? (boolean @*errors)]
(if has-errors?
(ui/border 0xFF0000
text-field)
text-field)))
So it looks like this strategy is slightly bit extra succesful? I’m unsure how nicely it converts into that incremental dream, by the best way.
React performs very nicely with dwell reload as a result of it does its factor in runtime. Mainly, it expects nothing from you upfront and due to that may do all kinds of loopy stuff with out reloading/recompiling/and so on. E.g. I can outline a brand new part and mount it into an current tree in runtime and never lose any state in different parts.
This property of React may be very interesting. I’m unsure how growth expertise is with incremental frameworks that require further compilation, however I assume it’s extra difficult.
Full restart can also be an possibility, so long as it occurs in the identical JVM and means that you can preserve at the least an extrernal state intact. Fortunately, Clojure works nicely for that.
For instance, after a sure threshold I switched from buffer evals for reload to full instruments.namespace
nuke & load as a result of handbook buffer evals have been changing into too advanced:
This unloads the namespace, masses it again, creates all new hierarchy of parts, and so on. All sooner than a single body on a 144 Hz monitor. Issues our computer systems can do in the event that they don’t should run Xcode!
Additionally, this strategy ought to be appropriate even with templates/preprocessing and it nonetheless offers you close-to-zero turnaround occasions and state persistence.
Seems to be like our business has converged on the next approaches:
- OOUI (previous classics)
- VDOM (“declarative”/“reactive” UI frameworks)
The place VDOM might be applied with:
- Templates
- Name-site positioning
- Return values
And knowledge group:
- High-down props drilling
- Reactivity
- Incremental computations (reactivity on steroids)
I’m leaning in the direction of VDOM + return values + incremental computations for Humble UI, however must run a number of experiments to see the way it feels and performs.
Additionally, I hope folks will discover this text by trying to find “React vs Svelte”. It’ll be so humorous.
Total, growing a UI framework is so fascinating, I’m studying a lot. It is best to strive it at some point, too.
Till that, take care. See you subsequent time!
Hello!
I’m Nikita. Right here I write about programming and UI design Subscribe
I additionally create open-source stuff: Fira Code, AnyBar, DataScript and Rum. Should you like what I do and need to get early entry to my articles (together with different advantages), you need to support me on Patreon.