š¶ RxJS+JSX framework experiment
or how to render a cat and a mouse the cool way
originally posted at dev.to
I like React. And I love RxJS. So I tried to mix them in a new framework:
tl;dr
Github repo: github.com/recksjs/recks š
Foreword
Iāve built this rendering engine in about a week for a hackathon. It turned out to be an interesting concept, that I wanted to share with you here!
The concept
React made DOM āfirst-class citizenā in our JS code (via virtual DOM). We can create vDOM anywhere in our structure and then pass it around.
Reactās components are basically a mapping of properties to vDOM:
Angular deeply integrated Observable streams and made them native to its components and services. Observables let us easily operate and coordinate async events and updates, spread in time.
In this framework, we (similarly to React) map properties to vDOM. Only here we fully control update and render streams. We take the input stream of props and map them to the output stream of vDOM:
Stream in. Stream out.
Letās get to examples, shall we?
Basic usage
Surely, we have to start with a āHello Worldā:
of
creates an Observable that emits a single provided value
Since our component renders a static <h1>
and never updates it ā we can skip the Observable part and simply return the element:
Looks react-ish, doesnāt it? Letās add more life to our components:
A Timer
timer(n, m)
emits a 0
at n
and then will emit consequent integers with m
interval
Again our component returns a stream of vDOM. Each time a component emits a value ā the vDOM is updated.
In this example, timer
will emit a new value every second. That value we will map
to a new vDOM, displaying each tick
in the <h1>
.
We can do this even simpler!
If a child in the vDOM is an Observable itself ā the engine will start listening to it and render its values in place. So letās move the timer
Observable right into the <h1>
:
This allows us to define more fine updates with neat syntax.
Note that the component function will be called only once. When the Observable timer(0, 1000)
emits a value ā the vDOM will be updated in place, without recalculating or updating other parts of the tree
State
When we need a local state in a component ā we can create one or several Subjects to write and listen to.
Subjects are Observables that also let us push values into them. So we can both listen and emit events
Hereās an example:
In the example above when the text field emits an input
event ā we push its value to name$
stream. view$
stream that we display derives from name$
input stream.
Note that we are using a startWith
operator for the view$
: to optimize rendering the engine waits for the first emission from all children before rendering them. So if we remove the startWith
ā <div>
will be rendered empty, until the view$
emits a value. Therefore we need to either add a startWith
operator or to wrap the Observable child with a static child, e.g. <span>{ view$ }</span>
And a more conventional example with a counter:
In this example again we have an input$
Subject that we'll push updates to. The view$
Observable accumulates emissions from the input$
using scan operator and will display our state. E.g. when we push 1, 1, 1
to the input$
ā we get a 1, 2, 3
on the view$
.
Refs or āreal DOM dealā
Sometimes we need to interact with DOM API. For that React uses specialref
objects, that contain a reference to the current DOM element in their current
property:
Of course in this framework, we get a stream of DOM references! Once a DOM element is created or replaced ā the engine pushes a new reference to the stream. We only need to provide the engine with a place for references to be pushed to ā a Subject. The engine will push the HTML element to it once it is attached to the real DOM. Thus we get a stream of HTMLElements
and can apply our logic either to each update or to the latest reference.
Here weāll focus the <input />
each time the <button/>
is clicked:
Subcomponents
So far we had components that only returned Observable results, and didnāt have to react to any input. Hereās an example of a parent component providing properties to a child component:
When a Parent
is rendering a Child
for the first time ā itās rendering <Child index={ 0 } />
. The engine will create a Child
and push the { index: 0 }
props object to the subcomponent's props$
Observable. The child will immediately react with a mouse š.
Later when the timer
ticks again and emits <Child index={ 1 } />
ā the engine will only push { index: 1 }
to the existing Child
props$
.
The Child
will now produce a cat š±.
And so on.
Redux
For bigger apps, weāll need a bit more sophisticated state management, then just a bunch of Subjects. Any implementation that outputs in an observable way would work with Recks! Letās try redogs state manager ā itās redux, redux-observable and typesafe-actions in one small package. Redogs outputs to an Observable, so weāll easily integrate it!
Letās be innovative and create a simple To Do List app as an example š
First, weāll create the store:
Now we can access the state changes of the store in our components:
Or dispatch events to it:
For brevity, Iāll skip showing reducers, effects, and other components here. Please, see the full redux app example at codesandbox.
Note that we donāt have to learn reselect
and re-reselect
APIs to interact with redux.
We donāt have to tweak proprietary static getDerivedStateFromProps()
or worry about UNSAFE_componentWillReceiveProps()
and UNSAFE_componentWillUpdate()
to be efficient with the framework.
We only need to know Observables, they are lingua franca in Recks.
Unlike React
For a React component to trigger a self-update ā it has to update its state or props (indirectly). React itself will decide when to re-render your component. If you want to prevent unnecessary recalculations and re-renderings ā there are several API methods (or hooks), that you can use to advice React how to deal with your component.
In this framework I wanted to make this flow more transparent and adjustable: you directly manipulate the output stream based on the input stream, using well known RxJS operators: filter, debounce, throttle, audit, sample, scan, buffer and many-many others.
You decide when and how to update your component!
Status
To try the framework, run:
git clone --depth=1 https://github.com/recksjs/recks-starter-project.git
cd recks-starter-project
npm i
npm start
Or you can run Recks in an online sandbox
The source code is published to github.com/recksjs/recks
The package is also available via npm i recks
, all you need is to set up your JSX transpiler (babel, typescript compiler) to use Recks.createElement
pragma.
[ Warning ] This is a concept, not a production-ready library.
Disclaimers
First of all, several times Iāve called this library a āframeworkā, yet this is no more of a āframeworkā than react is. So one might prefer to call it a ātoolā. Itās up to you š
Also, my comparisons to React are purely conceptual. React is a mature framework, supported by a smart team of professionals, surrounded by a brilliant community.
This one is a week old, built by me š¶
Alternatives
Thereās one library that provides a React hook to interact with Observables: rxjs-hooks. It works via auseState
hook to update the component's state each time an Observable emits, which triggers component update. Worth checking out!
Another elephant I should mention here is a real streams-driven framework: cycle.js by AndrƩ Staltz. It is a grown-up library, has a lot of supporters and solid integrations. Cycle.js has a bit different API of using subcomponents and interacting with DOM. Give it a try!
If you know other alternatives ā please, share š
Outro
Okay, thatās it!
Should I continue developing this project?
What features would you like to see next?
Iād love to know your thoughts, so leave a comment, please š
If you enjoyed reading this article ā give a push to the clap button: it will let me understand the usefulness of this topic and will help others discover this read.
In the following posts, weāll review other Recks integrations, I will share plans for features and publish project updates. So follow me here on medium and twitter to stay tuned!
Iām proud that youāve read so far!
Thank you