Composable Reactive UI — Preview

Well, he only concerns events, so we need something more powerful:h is like a Swiss knife: it’s able to configure any aspect of an element.

However the C in CRUI stands for composability, so we also offer a combinator for it:The result is the same, but please note that this version is slightly less performant, so it’s usually better to rely on h when possible.

ReactivityWe have a way to catch the user input, however, this kind of reactivity is not really declarative nor particularly reactive.

The core package is all about composability and building UIs, hence, it doesn’t care at all about reactivity by design, it just set the foundation to support it.

A nice consequence of this choice is that reactivity in CRUI is just a library and therefore can be implemented in many different flavors.

More interestingly, given that all implementations are built on top of the same abstraction, it should be possible to mix & match different styles.

The official R in CRUI comes from @crui/reactive package:Here we declare once again our input component, however, this time we bind its value with a StreamBox.

We decided to avoid the Virtual DOM and rather use Streams as reactivity building blocks.

A Stream is conceptually an Observable: every time the value in $box changes, it will notify all of its observers.

$bind will ensure that $box and input value are always synchronized, changing both sides accordingly.

This means that if we run:$box.

set('hello!')The input value will immediately change too.

SubmitNow that we have a nice way to catch user input, we need a button to trigger the creation of a new todo:The declaration part should be quite familiar by now, the important bit is to understand how to actually perform this action.

Conceptually, we need to grab the value from the input and then push it into some sort of todo list.

As in life, when you don’t know how to do something, just ask somebody else!The attentive reader will notice that we are also clearing todo, ensuring a nice UX.

Add More StructureExposing just what we need is the best approach if you want to release a component as a library, however, given that we want to build an app here, we can go a little further and give it more structure:Please keep in mind that the garbage collector will not be able to clean up Streams due to how the subscription mechanisms works, therefore it’s important to destroy them when not needed to avoid memory leaks.

AddTodoNow we have all we need to create the AddTodo:Todo ListWe need to display a list of all todos and we already have such a list, so we could be tempted to write something like:This will work for the first render, but again there will be no knowledge of reactivity.

We need another Stream and @crui/reactive offers one that is optimized to work with lists: StreamList.

type TodoList = StreamList<Todo>Our store now looks like:Next is the actual Todo list:TodoList is now reactive and every new todo will be automatically added to the DOM as expected.

Toggle Todo stateOur Todo currently is just text, but we also need to mark it as completed:You should by now appreciate how declarative this is.

By just looking at the type, you can infer that done property is meant to change through time, while text will stay the same.

Let’s update our TodoStore:And then our Todo component:A checkbox will be displayed near the todo text.

It’s worth mentioning that $bind is also able to bind the checked property from the input element, so every time we trigger it, the Todo done property will update accordingly.

We further abstracted the concept of having an element with CSS class and children in hcc .

As a quick note, every component also offers a way to easily declare a combination of them, which is how h is implemented.

FilterNext, we need to tackle the 3 filters.

To build them we first have to start from the concept of visibility, ie: what we can filter for.

enum Visibility { ALL, TODO, DONE,}A single filter can just be a button that will set the appropriate visibility:We will receive a Stream of Visibility and a Visibility represented by this particular Filter.

Something worth mentioning is that StreamBox.

map will create yet another StreamBox which will update accordingly with its parent stream, therefore is important to destroy it once the button element is not needed anymore.

To do so, we declare that className must be destroyed on the element cleanup phase.

Let’s now update the store to support visibility:We only included what we need to add to the previous TodoStore definition, so to not confuse the reader.

FiltersThis one is now straightforward:Filtered TodoListEven though we added the Visibility concept, our list has no knowledge of it.

Our list needs to react to 3 different user interactions:A new item is pushed in the listA new visibility filter is appliedA todo item state is toggledWe are covered for the first case and already have a stream for Visibility, so we can map that one into a stream of predicates:Predicate<T> = (val: T) => booleanOur first attempt could be:This could do the trick, but toggling the todo item state would not activate the filter logic again, so what we really need is what we call a Stream of Predicates Streams:$Predicate$<T> = Stream<Predicate$<T>>Predicate$<T> = (val: T) => Stream<boolean>TodoStore will then be:We already encountered the .

map , while .

clone will just create a copy of that stream and it’s useful to avoid other parts of the code mess-up the original stream.

We will better understand this in a second.

The hardest part is done, now we can just declare our TodoList to be filtered by a $Predicate$.

Luckily for us, there is a function that does exactly that:h$filter$$ is similar to map, but other than mapping items it will also filter them accordingly.

Given that it’s expecting the filter to generate a stream of booleans, it will also take care of destroying all of them once they are not needed anymore.

Switching filters will destroy the current filter logic together with all the streams used by it, that’s why is important to use .

clone.

One thing I would like to point out is that both this function and h$map will not recalculate the whole list every time there is a change, but rather will surgical update the DOM based on stream updates.

This means that the algorithmic complexity for adding/removing an element from the list is the same as Array.

prototype.

splice .

This was the last piece of the puzzle and with it, our implementation ends! You can see the full example on GitHub: CRUI TodosThank you!If you managed to read until here, thank you a lot and much appreciated!I hoped this at least piqued your interest.

I’m planning on releasing a couple more articles: one about how things work under the hood and another one explaining the motivations behind this library.

Stay tuned and see you next time!.. More details

Leave a Reply