If you landed here, I bet it's because of one of the following:
- You're watching what's happening with Signals and still wondering if you like them
- You've used Observables but you found using them confusing, problematic and boilerplated
- You feel something's not right using them for some weird reason (we'll get to that)
So, what is using Observables like, for instance, in React?
Let's start with the simple, boring, click-counter component:
import React, { useEffect, useRef, useState } from 'react';
import { fromEvent, Subscription } from 'rxjs';
import { map, scan } from 'rxjs/operators';
const ClickCounter: React.FC = () => {
const [count, setCount] = useState(0);
const buttonRef = useRef(null);
useEffect(() => {
const button = buttonRef.current;
if (!button) return;
const clickStream = fromEvent(button, 'click').pipe(
map(() => 1),
scan((acc, value) => acc + value, 0)
);
const subscription = clickStream.subscribe(setCount);
return () => {
subscription.unsubscribe();
};
}, []);
return (
<div>
<button ref={buttonRef}>Click me</button>
<p>Clicked {count} times</p>
</div>
);
};
export default ClickCounter;
- If you feel it's too much code just for a click counter, you're not alone.
- If you're not happy with having to call
.subscribe()
,unsubscribe()
, you may have your good reasons. - If you're sick of having to type
fromEvent(button, 'click')
for each of your event handlers, it's not just you. - If you feel it's total nonsense to have an Observable (which can do reactivity on its own) bridged through other idiosyncratic constructs like
useEffect
, you're clearly onto something. - If you're questioning whether Observables are the problem or React, or [other framework name here], you're really touching the essence of the problem.
Observable "bridge" libraries
You may be aware of some of those "bridge" libraries for Observables (react-rxjs, react-rx), which are most often third-party creations to remedy a certain framework's inability to handle streams.
Let's examine in another example whether we can see any improvement at all using them:
import {useState} from 'react'
import {useObservableEvent} from 'react-rx'
import {filter, map, tap} from 'rxjs/operators'
const ShowSliderValue = () => {
const [value, setValue] = useState(0)
const handleChange = useObservableEvent((value$) =>
value$.pipe(
filter(nonNullable),
map((value) => Number(value)),
tap(setValue),
),
)
return (<>
<input value={value} onChange={(event) => handleChange(event.target.value)}
/>
<div>Value is: {value}</div>
</>)
}
If you use RxJS because you understand functional-reactive principles, that tap(setValue)
operation above is certainly the part you will hate the most. Side effects are everywhere that just defeat one of the greatest benefits of FP: clean code.
What if...
Turns out several people had got enough. Some, like Angular, went to kind of drop Observables, while someone else came up with a new UI library supporting them in a way never done before.
import { BehaviorSubject, scan } from 'rxjs'
import { rml } from 'rimmel';
export const ClickCounterComponent = (initial = 0) => {
const counter = new BehaviorSubject( initial ).pipe(
scan( x => x+1 )
);
return rml`
<button onclick="${counter}">click me</button>
You clicked <span>${counter}</span> times.
`;
}
document.getElementById('root').innerHTML = ClickCounterComponent()
Intentionally tacit
This may be the first time you see an Observable stream referenced in different parts of the same template, as it's the new thing.
In Rimmel, the way an Observable is bound depends on where you put it in a template.
If it's in an event handler, like onclick="${stream}"
, it becomes an event source and every click will feed your stream.
If you put it anywhere else, like <div>${stream}</div>
it becomes a sink, and its output will feed the div
.
Many other bindings exist, so you can set class names with observables, <div class="class1 class2 ${stream}">
, data elements or other attributes.
Your code can become drastically shorter and Observables will now start to be a real pleasure to work with.
Conclusion: do Observables suck?
Actually, I think Observables are one of the greatest inventions in JavaScript since the if
statement.
The problem is not actually with Observables but with the lack of adequate frameworks supporting them... until now.
Rimmel.js is a UI library (dedicated to Observables that we can use today to make components, pages and webapps of any size and scale.
Most "state managers", Signals and useThings
, become quickly redundant when you have a powerful UI library that makes the best use of Observables and the FP paradigm, so you no longer have to compromise on code quality, testability, developer experience and least but not last, performance.
Are you comparing Observables to Signals?
Check out the next article about why signals suck even more to get another view of the same problem.