Error Handling in RxJS
Rate Limiting an Express App
How RxJS groupBy()
Works
Part 1: Why?
Look at a simple React counter:
1linkimport React, { useState, useEffect } from 'react';
2linkimport { render } from 'react-dom';
3link
4link
5linkfunction Timer() {
6link const [count, setCount] = useState(0); // --> create a state
7link useEffect(() => { // --> after each render
8link const interval = setTimeout(() => setCount(count + 1), 1000); // --> set a timeout to update the state
9link return () => clearTimeout(interval); // --> and clean the timeout as well
10link });
11link
12link return <div>You have been here for {count} seconds!</div>
13link}
14link
15linkrender(<Timer/>, document.body);
What happens here is:
React will invoke the function
Timer()
.
In that function, first a state is created using
useState()
, with a setter function also returned,setCount()
.Then, an effect is set-up, which will invoke that
setCount()
after a second of it being called.Finally, a
div element
is created and returned, which contains the value of the state created earlier, initially set to0
.React will render this element, and invoke the effect provided via
useEffect()
afterwards.
The effect invokes
setCounter()
providing a new value for our state.This triggers React to call the
Timer()
function again.However this time, the
useState()
function returns a different result, because React is keeping track of the order of what is being called by who.A new effect is setup (and the old one is cleaned up though not precisely at this time).
A new
div element
is created and returned, containing the new value of our state.React, instead of rendering the new element to the actual DOM, renders it to a virtual DOM, compares that virtual DOM with the actual DOM, and applies the diffs.
- Also afterwards, it will invoke the newly defined effect, which will continue the cycle.
Why this convoluted process, you might ask? Well, we wanted to be able to define
the visual representation of our counter, i.e. the div element
, in a simple and expressive manner:
1link<div>You have been here for {count} seconds!</div>
But the variable count
's value changes, so we need to track those changes and mirror it
in the visual representation, hence this overhead.
In other words, count
is a reactive value, i.e. its value changes in reaction to stuff
(e.g. passage of time), and React needs to go through all of that trouble to be able to mask that reactivity away.
The problem with this convoluted process is sort of obvious: Too much complexity. React has to go through all of this to mask the reactivity, as evident from this extremely simple example, causing the following issues:
In our example, React needs to execute all of the described process each second. The
component code (i.e. the Timer()
function) is being executed every second. A virtual DOM
is created and compared to the actual DOM every second.
Now this is not a problem for a fair number of cases. A lot of the time, the data-flow of your frontend code is not much more complicated than a simple timer. In such situations, React is, to quote Dan Abramov, fast enough. But quite naturally, if the data-flow complexity grows, it can easily fail to catch up.
Because of the complexity of the process, it is also pretty fragile. Which means there are a lot of stuff that you cannot do, or you would basically broke that process.
For example, run the code snippet from earlier, and you'll see React issuing this warning:
1linkWarning: render(): Rendering components directly into document.body is discouraged,
2linksince its children are often manipulated by third-party scripts and browser extensions.
3linkThis may lead to subtle reconciliation issues. Try rendering into a container element
4linkcreated for your app.
This is simply because React needs to maintain full control over the DOM-tree it renders the components to for all of the life-cycle of the component, as otherwise it might not be able to properly compare the Virtual DOM and the actual one.
Similarly, React needs to be able to keep track of your component's states and hooks, so
useState()
and useEffect()
functions become rather weird ones: You cannot use them inside
a condition or a loop, or they might break not only the functionality of your component, but also
other components that React is handling.
Note that these are natural, unavoidable compromises in exchange for masking the reactivity. So IF it was the case that directly dealing with the reactive nature of our timer was a really hard thing to do, then it would make sense to adopt such a convoluted process and deal with its cons as well.
And when React was created, that indeed was the case. But is it still?
info ABOUT OTHER FRAMEWORKS
So for this piece I picked React as a point of comparison. But the essential problems mentioned here are inherent to ALL well-known frameworks, as they are all designed around the idea of masking the reactivity.
For example, Angular avoids using a virtual DOM and is generally faster than React. Its solution is basically recalculating scope variables upon a huge number of events (e.g. it outright overrides browser's
setTimeout()
function to hook into it) and then applying the necessary changes accordingly.And while that similarly works fine for simpler situations, Angular is basically trading a lot of the already limited control you would have with React for some marginal performance gain. Ironically, that strategy quickly backfires as it is much harder to optimize Angular's change detection in cases where you've got pretty complicated data-flows.
Let's take a deeper look at the nice representation we want to achieve that causes so much trouble:
1link<div>You have been here for {count} seconds!</div>
This expression is basically describing the following DOM (sub-)tree:
1linkDIV
2link| --- TEXT:: You have been here for
3link| --- VAR:: {count}
4link| --- TEXT:: seconds!
If all of the child nodes of DIV were TEXT nodes (or other HTML nodes), then we could simply
use the browser's own APIs to render our <div>
. However, count
is a variable that changes over time.
How should it be handled?
React's solution basically boils down to this:
count
as a plain integer in the component scopesetCount()
setter functioncount
changes, recalculating the sub-treeBut what if instead of masking count
's reactive nature, we represent it with a proper reactive entity, e.g.
RxJS's Observables?
Well, if count
was an Observable instead of a plain integer, we could simplify its rendering process:
count
count
count
emits.With this solution, we would not need to re-run the component code everytime count
's value changes. In fact,
we would need to execute the component code exactly once.
Additionally, we would not need to diff the returned sub-tree with what is already rendered. We already are changing
the only place in the DOM tree that needs to change (the text content of the TEXT node we created for count
).
Look at this modified, hypothetical code:
1linkimport { render } from 'yet-another-frontend-framework';
2linkimport { BehaviorSubject } from 'rxjs';
3link
4link
5linkfunction Timer() {
6link const count = new BehaviorSubject(0); // --> create an Observable
7link setInetrval(() => count.next(count.value + 1), 1000); // --> emit its next value on an interval
8link
9link return <div>You were here for {count} seconds!</div>;
10link}
11link
12linkrender(<Timer/>, document.body);
Now our rendering process becomes:
Call the
Timer()
function
It creates an Observable,
count
It sets up an interval, emitting the next value for
count
each secondIt returns the
div element
, which contains a TEXT node subscribed tocount
Render that
div element
on the document.
Quite simple, isn't it?
This simple process is not just potentially much faster (less calculations), it is also much more stable and flexible due to its inherent simplicity. There is no virtual DOM, and the component is rendered exactly once, so you can easily mess with all parts of the DOM without breaking anything, which in turn brings high levels of integratibility for your components.
Additionally, as the component code is only executed once, there is no need to keep track of any state or hook, so you do not have the weird and easy-to-break machinery of React hooks. I mean, you can even bring the whole process out of the scope of any component and gain the same result:
1linkimport { render } from 'yet-another-frontend-framework';
2linkimport { BehaviorSubject } from 'rxjs';
3link
4link
5linkconst count = new BehaviorSubject(0); // --> create an Observable
6linksetInetrval(() => count.next(count.value + 1), 1000); // --> emit its next value on an interval
7linkrender(<div>You were here for {count} seconds!</div>, document.body);
To top it off, lets even use RxJS's own timer()
function which directly gives
us an Observable emitting values each second:
1linkimport { render } from 'yet-another-frontend-framework';
2linkimport { timer } from 'rxjs';
3link
4link
5linkrender(<div>You have been here for {timer(0, 1000)} seconds!</div>, document.body);
So to recap:
Current frameworks and tools are designed around masking reactivity,
It is actually easy to do reactive logic using modern tools such as RxJS
Which means there is no need for those compromises.
So with this reasoning, I embarked on creating yet another frontend framework (or library). You can checkout the result here. The library itself is pretty young, and in dire need of testing, benchmarking and optimization. But it has already helped me a lot with my frontend efforts (for example it powers these blogs and all oss documentation that I do through CODEDOC).
If you enjoyed this piece, stay tuned for upcoming parts where we'll go through the story of how it was designed and created!
Hero Image by Wendy Scofield from Unsplash.
Hero Image by Hafidh Satyanto from Unsplash.
Short-comings of React and other frontend frameworks, i.e. why CONNECTIVE HTML was created.
Part 1: Why?
React, Angular, Frontend, reactive, RxJS, performance, overhead, React Hooks, web design, javascript, JSX