Modern frontend development often turns into a battle with async code:
- async/await
- subscriptions
- dependency arrays
- memoization
- race conditions
- unnecessary recomputations
What if you could simply describe your data and let the system
handle the rest?
That's exactly the idea behind rs-x.
To demonstrate it, I built a small demo that tracks the real-time
position of the International Space Station (ISS).
👉 Live demo: https://robert-sanders-software-ontwikkeling.github.io/rs-x/demo
The Idea
The demo polls a public ISS API and exposes the data as part of a
reactive model. ⚠️ Note > If many people open the demo simultaneously, the public ISS API may return Too Many Requests errors due to rate limits.
It returns:
- timestamp
- latitude
- longitude
- altitude
- velocity
At the same time the model contains an expensive computation to
demonstrate how rs-x handles dependencies.
The key idea:
Only recompute what actually depends on changed data.
The Demo
The API is polled every few seconds using RxJS.
const $ = api.rxjs;
const rsx = api.rsx;
const issRaw$ = $.interval(2000).pipe(
$.startWith(0),
$.switchMap(() =>
$.from(
fetch('https://api.wheretheiss.at/v1/satellites/25544')
.then((r) => r.json())
)
),
$.map((data) => ({
ts: Number(data.timestamp ?? 0),
lat: Number(data.latitude ?? NaN),
lon: Number(data.longitude ?? NaN),
altKm: Number(data.altitude ?? NaN),
velKph: Number(data.velocity ?? NaN),
})),
$.shareReplay({ bufferSize: 1, refCount: true })
);
Derived Reactive Data
Next we derive a stream that only emits when the ISS position
meaningfully changes.
const geoInputs$ = issRaw$.pipe(
$.map((x) => ({
latQ: Math.round(x.lat * 100) / 100,
lonQ: Math.round(x.lon * 100) / 100,
})),
$.distinctUntilChanged(
(a, b) => a.latQ === b.latQ && a.lonQ === b.lonQ
)
);
This prevents unnecessary recomputation.
The Reactive Model
Now we define the model.
const model = {
iss: issRaw$,
geo: geoInputs$,
heartbeat: 0,
expensiveRuns: 0,
expensiveGeoScore(lat, lon) {
model.expensiveRuns++;
let acc = 0;
for (let i = 0; i < 3_000_000; i++) {
acc += Math.sin(i);
}
const score =
Math.round((Math.abs(lat) * 1.7 + Math.abs(lon) * 0.9) * 100) / 100;
return score;
},
};
The function intentionally simulates a CPU-heavy computation.
A Completely Declarative Expression
Finally we describe the data we want.
return rsx(`
({
heartbeat,
issTs: iss.ts,
altKm: iss.altKm,
velKph: iss.velKph,
lat: iss.lat,
lon: iss.lon,
geoScore: expensiveGeoScore(geo.latQ, geo.lonQ),
expensiveRuns
})
`)(model);
That's it.
What Happens When Data Changes?
Two independent updates happen in the demo.
ISS position updates
When the ISS position changes:
- lat
- lon
The expensive computation runs again.
Heartbeat updates
A separate part of the model changes every few seconds.
setInterval(() => {
model.heartbeat++;
}, 5000);
This does not trigger the expensive computation.
Why?
Because the expression only depends on:
- geo.latQ
- geo.lonQ
rs-x tracks these dependencies automatically.
Why This Matters
In many systems you need to manually manage recomputation:
- useMemo
- useEffect
- dependency arrays
- caching
- manual optimization
With rs-x the rule is simple:
Describe your data and its relationships.
The runtime builds a reactive dependency graph and recomputes only
what is required.
The Mental Model
Think of it like a spreadsheet.
If cell A1 changes:
- only cells that depend on A1 update
- unrelated cells remain untouched
rs-x brings this model to JavaScript expressions.
The Result
The demo creates a reactive computation graph that:
- handles async streams
- tracks dependencies automatically
- avoids unnecessary recomputation
- keeps code declarative and simple
No async orchestration.
No manual dependency tracking.
Just describe the data.
Support the Project
If you like rs-x, consider supporting the project:
https://github.com/sponsors/robert-sanders-software-ontwikkeling
It helps me continue building open source tools for developers.
Top comments (0)