Sponsor
Star

Created With

Fast and precise reactive state management for JavaScript / TypeScript, in a flexible and unopinionated manner. Make changes to any part of your state tree, track changes, subscribe to specific node/sub-tree, track changes by entity keys, verify changes, etc.

1linknpm i rxdeep

Other Installation Methods

linkQuick Tour

Create a state:

1linkimport { state } from 'rxdeep';

2linkconst s = state([ { name: 'John' }, { name: 'Jack' }, { name: 'Jill' } ]);


Listen to a sub-state:

1links.sub(1).sub('name').subscribe(console.log); // --> subscribes to property `name` of object at index 1 of the array


Modify root state:

1links.value = [ { name: 'Julia' }, ...s.value ]; // --> logs `John`, since `John` is index 1 now

... or mid-level states:

1links.sub(1).value = { name: 'Josef' }; // --> logs `Josef`

... or leaf-states on the same address:

1links.sub(1).sub('name').value = 'Jafet'; // --> logs `Jafet`


Verify changes to the state:

1linkimport { state, verified } from 'rxdeep';

2link

3linkconst v = verified(state(12), change => change.from < change.to); // --> only increasing numbers

4link

5linkv.subscribe(console.log);

6link

7linkv.value = 10; // --> logs 12

8linkv.value = 14; // --> logs 14

9linkv.value = 9; // --> logs 14

10linkv.value = 13; // --> logs 14

11linkv.value = 15; // --> logs 15


A state is an Observer:

1linkimport { interval } from 'rxjs';

2linkimport { map } from 'rxjs/operators';

3link

4linkinterval(1000)

5link.pipe(map(i => ({ name: `Jarvis #${i}`})))

6link.subscribe(s.sub(1)); // --> logs `Jarvis #0`, `Jarvis #1`, `Jarvis #2`, ...


A state is an Observable:

1linkimport { debounceTime } from 'rxjs/operators';

2link

3links.sub(1).pipe(debounceTime(1000)).subscribe(console.log); // --> debounces changes for 1 second


Track keys instead of indexes:

1linkimport { state, keyed } from 'rxdeep';

2link

3linkconst k = keyed(state(

4link [{ id: 101, name: 'Jill' }, { id: 102, name: 'Jack' }]

5link ), p => p.id

6link);

7link

8linkk.key(101).sub('name').subscribe(console.log); // --> logs `Jill`

9link

10linkk.value = [k.value[1], k.value[0]]; // --> no log

11linkk.sub(1).sub('name').value = 'John'; // --> logs `John`


Track index of a specific key:

1linkk.index(101).subscribe(console.log); // --> logs 0, 1


linkUI Frameworks

RxDeep is not by any means limited to frontend use. However most backend designs are state-less or with dedicated services managing states, which means its most common use-case is in the frontend.

RxDeep is also completely framework agnostic, with a precise emission system that allows surgical updates even if you are using pure JavaScript without the need for a Virtual DOM, passive change detection, etc.

These precise emissions also should reduce the change detection / DOM reconcilliation load on most popular frameworks, as re-renders can be requested only when something has truly changed, and specifically on the DOM sub-tree that have really changed.


link React

You can use RxJS-hooks for fetching and rendering states in React components. This should also result in better performance as it should reduce number of redundant tree diffs conducted by React.

1linkimport { useObservable } from 'rxjs-hooks';

2link

3linkconst s = state(...);

4link

5linkfunction MyComponent(...) {

6link const value = useObservable(() => s.sub('something')) || {};

7link return <div>{value.property}</div>

8link}

Real World Example

link Angular

You can use Angular's async pipe to render states or sub-states:

1link<div>{{s | async}}</div>

2link<div>{{s.sub('property') | async}}</div>

You can also utilize Angular's [(ngModel)] syntax to bind states to inputs:

1link<input [(ngModel)]="s.sub('property').value" type="text" />

Real World Example

link Vue.js

You can use vue-rx to render states in your Vue apps, which should (I don't know if it would) yield similar performance improvements:

1linkimport VueRx from 'vue-rx';

2linkVue.use(VueRx);

3link

4linknew Vue({

5link el: '#app',

6link subscriptions: {

7link s,

8link name: s.sub('name')

9link }

10link});

1link<div>{{ s }}</div>

2link<div>{{ name }}</div>

You can use v-model syntax to directly bind inputs and states:

1linknew Vue({

2link ...

3link subscriptions: { ... },

4link data: { state }

5link})

1link<input type="text" v-model="s.sub('name').value"/>

Real World Example

link Pure JavaScript

As mentioned above, RxDeep provides precision change emission, which means you can directly listen to the state changes and surgically update DOM tree accordingly:

1links.sub('name').subscribe(name => nameElement.textContent = name);

Specifically, you can utilize KeyedState (keyed()) and its .changes() method to receive detailed array changes that enable you to precisely modify dynamic DOM trees based on collections and arrays.

Real World Example

linkFeatures

Here are the design goals/features of RxDeep, setting aside from other reactive state management libraries:


linkPerformance

RxDeep is extremely fast and light-weight in terms of memory consumption and computation, utilizing pure (multicasted) mappings on the root of the state-tree for reading/writing on the whole tree.

Note that user-provided functions are utilized during particular operations, which might result in some loss of performance if said functions aren't fast enough.

Learn More

linkPrecision

RxDeep enables subscribing to a particular sub-state of the state tree. These sub-states only emit values when the value of the sub-state has changed, or when there is a change issued directly to them, a state with the same address, or one of their descendants.

This means you could subscribe heavy-weight operations (such as DOM re-rendering) on sub-states.

Learn More

linkFlexibility

RxDeep, unlike libraries such as Redux, doesn't require your changes to be funneled through specific channels. You can freely issue changes to any part of the state-tree, so for example you can only expose relevant parts of the state-tree to modules/components.

The only limitations (similar to Redux) are that you need to keep state as plain JavaScript objects (number | string | boolean | undefined | Date, or arrays and plain objects of these values), and respect object immutability. Basically do not change an object without changing its reference.


DON'T:

1links~.~value~~~~~.~push~~~~(~x~)~; // --> WRONG!

2links~.~value~~~~~.~x ~~=~ y~~; // --> WRONG!

DO:

1links.value = s.value.concat(x); // --> CORRECT!

2links.sub('x').value = y; // --> CORRECT!

3links.value = { ...s.value, x: y } // --> CORRECT!

Learn More

linkChange History

State tree is kept in sync by tracking changes (via Change objects). This simply means you can track changes directly, record them, replay them, etc.

1links.downstream.subscribe(console.log); // --> Log changes

2links.sub(1).sub('name').value = 'Dude';

3link

4link// This object will be logged:

5link{

6link value: [{...}, { name: 'Dude', ... }, ...],

7link trace: {

8link subs: {

9link 1: {

10link subs: {

11link name: { from: ..., to: 'Dude' }

12link }

13link }

14link }

15link }

16link}

Learn More

Furthermore, keyed states provide detailed array changes, i.e. additions/deletions on particular indexes, or items being moved from one index to another.

1linkconst k = keyed(state(

2link [{ id: 101, name: 'Jack' }, { id: 102, name: 'Jill' }]

3link ), p => p.id

4link);

5link

6linkk.changes().subscribe(console.log); // --> Log changes

7link

8linkk.value = [

9link { id: 102, name: 'Jill' },

10link { id: 101, name: 'Jack' },

11link { id: 103, name: 'Jafet' }

12link];

13link

14link// This object will be logged:

15link{

16link additions: [{

17link index: 2,

18link item: { id: 103, name: 'Jafet' }

19link }],

20link deletions:[],

21link moves:[

22link { oldIndex: 0, newIndex:1, item: { id: 101, name: 'Jack'} },

23link { oldIndex: 1, newIndex:0, item: { id: 102, name: 'Jill'} }

24link ]

25link}

Learn More

linkChange Verification

You can verify changes occuring on the state-tree (or on a particular sub-tree). RxDeep will utilize the change history to revert unverified changes on affected sub-states:

1linkconst v = verified(

2link state([{ val: 21 }, { val: 22 }, { val: 23 }]),

3link change => change.value.reduce((t, i) => t + i.val) % 2 === 0

4link);

5link

6linkv.sub(0).sub('val').value = 22; // --> change denied, local changes automatically reverted

7linkv.sub(0).sub('val').value = 23; // --> change accepted and routed through the state-tree

Learn More

linkExtensibility

An RxDeep state is an RxJS Observable and an Observer, providing great interoperability with lots of existing tools.

Additionally, each state basically relies on a downstream observable and an upstream observer for keeping track of changes and keeping data in sync. By providing custom downstream / upstreams, you can greatly extend RxDeep for use in any particular use case (for example you can easily distribute state-trees across a network).

Learn More

linkThin and Type Safe

RxDeep has a bundle size of ~8Kb, which includes its only dependency RxJS (tree-shaken). Since RxJS is already included in lots of frontend bundles, contribution of RxDeep to your bundle size will most probably be under 2Kb (which is the raw library without dependencies).

This small size is due to extremely thin API surface of the library, focusing only on providing deep state management and minor utilities for that. This in turn makes RxDeep pretty easy to learn.

RxDeep is written in TypeScript with detailed type annotations, which should greatly improve development experience even if you use it in JavaScript (error highlighting, autocompletes, etc).

Quick TourUI Frameworks React Angular Vue.js Pure JavaScriptFeaturesPerformancePrecisionFlexibilityChange HistoryChange VerificationExtensibilityThin and Type Safe

Home How to Install State Key Tracking Change Verification Change Performance Precision