React(ive) state management


Enter Observables
React's reactivity



Reactivity in real life

Reactivity in modern web



Heinrich Apfelmus
"The essence of a functional reactive programming is to specify the dynamic behavior of the value completely at the time of declaration."
Reactivity refers to how an application automatically updates the UI whenever application state changes.
Why so Reactive?
Automatic Updates
Improved DX
Enhanced UX
Reduced Code Complexity
03
02
04
01


Coerced v.s. Fine-grained Reactivity




- Simpler approach
- Entire UI re-renders whenever the state changes
- More granular control over UI updates
- Updates the specific parts of the UI that are affected by the state change







Enter Signals


Nikola Mitrović
Development Lead & Technical Architect
Vega IT
Novi Sad, Serbia



Tech Speaker

State management challenges
Scalability
Predictability
Performance
Maintainability
02
04
Developer Experience
03
01
05

Ratings

- Excellent 🚀
- Very good 👍
- Ok 👌
- Meh 😒
- Would not recommend 👎
React

React
Surgical precision 👎
Consistent state 😒
Performance 👎
Easier mental model 😒
Clean dev experience 🚀🚀
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={incrementCount}>Increment</button>
</div>
);
}
export default Counter;


SolidJS

SolidJS
Surgical precision 🚀
Consistent state 🚀
Performance 🚀🚀🚀
Easier mental model 🚀🚀
Clean dev experience 👍
import { createSignal, onCleanup } from 'solid-js';
function Counter() {
const [count, setCount] = createSignal(0);
const increment = () => setCount(count() + 1);
// Clean up (e.g., clearing intervals) on component unmount
onCleanup(() => console.log('Counter component unmounted!'));
return (
<div>
<button onClick={increment}>Count: {count()}</button>
</div>
);
}


Enter RxJS
RxJS is a library that enables reactive programming in JavaScript. This means working with data streams and events in a more streamlined and declarative manner.



Observables at the Core
The fundamental building block of RxJS is the Observable. An Observable represents a stream of values (events or data) that can arrive over time.



React-RxJS

React-RxJS


Targeted re-renders 🚀
Consistent state 🚀
Performance 😒
Easier mental model 👍
Clean dev experience 😒
import { bind } from "@react-rxjs/core";
import { createSignal } from "@react-rxjs/utils";
import { shareReplay, takeWhile } from "rxjs/operators";
const [counterChange$, setCounter] = createSignal<number>();
const counterLimit$ = counterChange$.pipe(
takeWhile((value) => value < 10),
)
const [useCounter] = bind(
counterChange$.pipe(
shareReplay(1)
),
0
);
export { useCounter, setCounter };
Entry point to
React-RxJS
retry
Recipe request
Cache wins race

Pull v.s. push




React-RxJS docs
"Historically, React uses a pull-based architecture. On the other hand, RxJS uses a push-based approach, where you declaratively define streams and their relationships, and RxJS will propagate every change from one stream to the next one. React-RxJS bridges the gap between these two behaviors."
Enter MobX

Signal-like, targeted re-rendering approach
Minimal boilerplate + Flexibility + DevTools
Simplicity
Performance



MobX

Enter MobX
import { action, makeObservable, observable } from 'mobx';
class AppState {
constructor() {
makeObservable(this); // Enable MobX observability
}
@observable count = 0;
@action increment() {
this.count++;
}
}
export const appState = new AppState();

Surgical precision 🚀
Consistent state 🚀
Performance 👍
Easier mental model 😒
Clean dev experience 🚀


MobX in React 19 (with Compiler)



Some libraries hack up React internals to make signals and fine-grained reactivity work, which often doesn’t work on all platforms and may break if React internals change.

React 19 - Forget Compiler




How much did we move the needle?











Virtual DOM
Zone.js
Dirty Checking


Enter Legend State Observables
Fine-grained reactivity 🔥
No boilerplate 👍
Designed for maximum performance and scalability ⚡️
Super small at 4kb 🐥
Super easy to use 😌






Legend State

Legend State


import { Memo } from "@legendapp/state/react";
import { count$ } from "../state";
const count$ = observable(0);
export const BasicCounter = () => {
const handleClick = () => count$.set((v) => v + 1);
return (
<div>
<div>
Legend Count: <Memo>{count$}</Memo>
</div>
<button onClick={handleClick}>Add count</button>
</div>
);
};
Surgical precision 🚀
Consistent state 🚀
Performance 🚀
Easier mental model 🚀🚀
Clean dev experience 👍
Resources


https://www.builder.io/blog/unified-reactivity-theory
https://www.builder.io/blog/history-of-reactivity
https://www.youtube.com/watch?v=R5AcOtxIdMk
https://www.youtube.com/watch?v=cPVbfiAEbac
https://www.youtube.com/watch?v=l-0fKa0w4ps
https://dev.to/ryansolid/building-a-reactive-library-from-scratch-1i0p
https://legendapp.com/open-source/legend-state/
TanStack Query, Meet RxJS: Building Your Own TanStack Query with Angular & RxJS


Thank you!
n.mitrovic@vegait.rs
You can find me at
Link to the slides



https://github.com/nmitrovic92/reactive-state-managements
(React)ive state management
By nmitrovic
(React)ive state management
- 295