Sponsor
Star

Created With

linkState

A state represents a value that can change, i.e. a reactive value (as in "it reacts to stuff"):

1linkimport { state } from 'rxdeep';

2link

3linkconst a = state(42);

4linkconst b = state([1, 2, 3, 4]);

5linkconst c = state({

6link name: 'Awesome Team',

7link people: [

8link { id: 101, name: 'Jeremy' },

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

10link ],

11link ...

12link})

You can also create states using the State class constructor:

1linkimport { State } from 'rxdeep';

2link

3linkconst a = new State(42);


A state is an Observable, so you can subscribe to it and read its values as they change over time:

1linkconst a = state(42);

2linka.subscribe(console.log); // --> log values from a

3link

4linka.value = 43; // --> logs 43

5linka.value = 44; // --> logs 44

6linka.value = 44; // --> logs 44

7link

8link// Logs:

9link// > 42

10link// > 43

11link// > 44

12link// > 44


You can change the value of a state using its .value property:

1linka.value = 44;

Or using its .next() method:

1linka.next(44);


You can also read current value of a state using its .value property:

1linkconst a = state(42);

2linka.subscribe(); // --> this is important!

3link

4linkconsole.log(a.value); // --> logs 42

5linka.value = 43;

6linkconsole.log(a.value); // --> logs 43

7link

8link// Logs:

9link// > 42

10link// > 43

warning WARNING

.value will not be up to date with state's latest changes unless the state is subscribed to, which means either .subscribe() method of the state should have been called or that of one of its sub-states (or proxy states).

So this will NOT work:

1linkconst a = state(42);

2linka.value = 43;

3linkconsole.log(a.value); // --> logs 42!!!

4link

5link// Logs:

6link// > 42


A state is also an Observer, which means it can subscribe to another Observable:

1linkimport { interval } from 'rxjs';

2link

3linkconst a = new Subject(0);

4linkinterval(1000).subscribe(a);


linkObject Immutability

ALWAYS make changes to the state that respect object immutability. You MUST always ensure that you are changing the reference when changing the value of a state.

This happens automatically with raw values (number, boolean, string, etc.). For more complex values (objects and arrays), use the rest operator ... or methods that create a new reference.


DON'T:

1links~.~value~~~~~.~push~~~~(~x~)~;

DO:

1links.value = s.value.concat(x);

2link// -- OR --

3links.next(s.value.concat(x));


DON'T:

1links~.~value~~~~~.~x ~~=~ ~42~~;

DO:

1links.value = { ...s.value, x: 42 }

2link// -- OR --

3links.sub('x').value = 42;

4link// -- OR --

5links.next({ ...s.value, x : 42 });

6link// -- OR --

7links.sub('x').next(42);


linkSub-States

Take this state:

1linkconst team = state({

2link name: 'Awesome Team',

3link people: [

4link { id: 101, name: 'Julia' },

5link { id: 102, name: 'Jeremy' },

6link ]

7link})

The whole team is a value that changes over time, but so is its .name, or its .people. Also the .length of the .name is a value that changes over time, so is the first person in .people list.

You can represent all these reactive values with State objects as well, using team's .sub() method:

1linkconst name = team.sub('name');

2link

3linkconst nameLength = name.sub('length');

4linkconst nameLength = team.sub('name').sub('length');

5link

6linkconst people = team.sub('people');

7link

8linkconst firstPerson = people.sub(0);

9linkconst firstPerson = team.sub('people').sub(0);

10link

11linkconst firstPersonsName = people.sub(0).sub('name');

12linkconst firstPersonsName = team.sub('people').sub(0).sub('name');

In this example, team is the parent state of all (also called the root state), and name is its sub-state (or child state). nameLength is also a sub-state of name, you could call it a grandchild of team.

You can use sub method with any possible key (index, string key, symbol, etc) of the objects of the parent state. The result is another state object, reflecting the state of that particular property.


Sub-states pick up changes made to their parent states:

1linkconst team = state({

2link name: 'Awesome Team',

3link people: [

4link { id: 101, name: 'Julia' },

5link { id: 102, name: 'Jeremy' },

6link ]

7link});

8link

9linkteam.sub('name').sub('length').subscribe(console.log);

10linkteam.value = {

11link name: 'That Other Team',

12link people: [...]

13link};

14link

15link// Logs:

16link// > 12

17link// > 15


A sub-state only emits values when its value has really changed:

1linkteam.sub('people').sub(0).sub('name').subscribe(console.log);

2link

3linkteam.value = {

4link name: team.value.name, // --> do not change the name

5link people: [

6link {id: 101, name: 'Julia'},

7link {id: 103, name: 'Jaber'},

8link {id: 102, name: 'Jeremy'},

9link ]

10link}

11link

12linkteam.sub('people').sub(0).value = { id: 104, name: 'Jin' }

13link

14link// Logs:

15link// > Julia

16link// > Jin

Or when a change is issued at the same address of the tree:

1linkteam.sub('people').sub(0).sub('name').subscribe(console.log);

2linkteam.sub('people').sub(0).sub('name').value = 'Julia';

3link

4link// Logs:

5link// > Julia

6link// > Julia


You can make changes in sub-states while listening to them on parent states:

1linkconst team = state({

2link name: 'Awesome Team',

3link people: [

4link { id: 101, name: 'Julia' },

5link { id: 102, name: 'Jeremy' },

6link ]

7link});

8link

9linkteam.subscribe(console.log);

10linkteam.sub('name').value = 'That Other Team';

11link

12link// Logs:

13link// > { name: 'Awesome Team', people: [...] }

14link// > { name: 'That Other Team', people: [...] }

touch_app IMPORTANT

It is a good idea to ensure sub-states are subscribed to before you change their value. The .value property of a sub-state might also be out of sync if it is not subscribed to. When you change its value, it will issue a change to the state-tree regardless of whether or not it is subscribed, the change might have the wrong history if .value is not in sync.


linkValue Types

RxDeep requires the state-tree to be represented by plain JavaScript objects, i.e. numbers, booleans, strings, Date objects, undefined, null, or arrays / objects of other plain JavaScript objects, without any circular references:

1linktype PlainJavaScriptObject = null | undefined | number | string | boolean | Date

2link | PlainJavaScriptObject[]

3link | {[key: string]: PlainJavaScriptObject}

This is essential since otherwise RxDeep is unable to perform post-tracing on changed objects.


linkChange History

You can subscribe to .downstream property of a state to listen for changes occuring to it (instead of just updated values):

1linkteam.sub('people').downstream.subscribe(console.log);

2linkteam.sub('people').sub(0).sub('name').value = 'Jackie';

3link

4link// Logs:

5link// > {

6link// > value: [ { id: 101, name: 'Jackie', id: 102, name: 'Jeremy' } ],

7link// > trace: {

8link// > subs: {

9link// > people: {

10link// > subs: {

11link// > 0: {

12link// > subs: {

13link// > name: { from: 'Jeremy', to: 'Jackie' }

14link// > }

15link// > }

16link// > }

17link// > }

18link// > }

19link// > }

20link// > }

Read More About Changes

linkUnder the Hood

A state is constructed with an initial value, a downstream (Observable<Change>), and an upstream (Observer<Change>). Downstream is basically where changes to this state come from. Upstream is where this state should report its changes.

1link// In this example, the value of `s` is updated with a debounce, so

2link// changes made in rapid succession are supressed.

3link

4linkconst echo = new Subject<Change<number>>();

5linkconst s = new State(3, echo.pipe(debounceTime(300)), echo);

6link

7links.subscribe(console.log);

8link

9links.value = 4; // --> gets supressed

10links.value = 10; // --> gets through

11linksetTimeout(() => s.value = 15, 310); // --> gets through

12linksetTimeout(() => s.value = 17, 615); // --> gets supressed

13linksetTimeout(() => s.value = 32, 710); // --> gets through

14link

15link// Logs:

16link// > 3

17link// > 10

18link// > 15

19link// > 32

1link// In this example, the value of `s` will remained capped at 10.

2link

3linkconst echo = new Subject<Change<number>>();

4linkconst s = new State(3,

5link echo.pipe(map(change => ({

6link ...change,

7link value: Math.min(change.value!!, 10),

8link }))),

9link echo

10link);

11link

12links.subscribe(console.log);

13link

14links.value = 4; // --> ok

15links.value = 12; // --> changes to 10

16links.value = 9; // --> ok

17links.value++; // --> ok

18links.value++; // --> caps

19link

20link// Logs:

21link// > 3

22link// > 4

23link// > 10

24link// > 9

25link// > 10

26link// > 10


When a new value is set on a state, it creates a Change object and passes it up the upstream. The state WILL NOT immedialtely emit said value. Instead, it trusts that if the value corresponding to a particular change should be emitted, it will eventually come down the downstream. Thats how we are able to modify the value of requested changes in above examples.

The upstream and downstream of sub-states is set by the parent state. When a change is issued to a sub-state, the parent state will pick up the change, add the corresponding sub-key to the change trace, and send it through its own upstream. When a change comes down its downstream, it will match it with sub-states using the sub-key specified in the change trace and down-propagate it accordingly, removing the head of the change trace in the process.

touch_app IMPORTANT

For performance reasons, a state will change its local .value when it is sending changes up the upstream, but will not emit them. This means if you want to completely reverse the effect of some particular change, you should emit a reverse change object on the downstream in response. Simply ignoring a change will cause the state's value to go out of sync with the change history and the emission history.

Learn More

info NOTE

Note that if you want to create a state by providing specific upstream and downstream parameters, you need to use State class constructor.


linkPost-Tracing

When a change is issued to a non-leaf node, the change trace that is collected is up to that particular point of the state-tree, and not further down. As a result, a state does not know how to down propagate this change to its sub-states.

In such a case, the state will actually retrace the change based on given values, adding a complete trace to the change object, and then routing it accordingly. This operation only happens at the depth that the change was made, since further down the trace is complete down to leaf-states. Additionally, due to efficient object-tree diffing, this single operation does not slow down the propagation of change by any means. Checkout the post on performance to see how in details, but intuitively if the change is not properly traced, then all of the sub-tree would have to emit it in order to not be lossy (not emit when something is truly changed), which equals to a full sweep of the sub-tree, which could be used to diff the sub-tree instead.

Learn More
StateObject ImmutabilitySub-StatesValue TypesChange HistoryUnder the HoodPost-Tracing

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