States
States are the core data containers in the reactive store, responsible for maintaining and broadcasting data updates to multiple subscribers. Like emitters, states can create and manage reactive streams to broadcast changes efficiently. They are designed to simplify state propagation and synchronization across stores or features, ensuring consistency. States can also integrate seamlessly with other reactive sources like emitters, states, and observables.
Creating a State
Basic State
To create State, use the state function:
import {state} from '@bitfiber/rx';
const counter = state<number>(0);
State with Interaction Logic
To add interaction logic with other reactive sources, use the onInit
callback. This ensures
subscriptions and emissions occur only after the store is initialized:
const receiver = state<number>(0, s => s
// Add interaction logic here
.receive(source));
const source = emitter<number>();
Getting Current Data
States allow you to get current data using get method or calling the state like a function:
const counter = state<number>(1);
const value1 = counter(); // Expected result: 1
const value2 = counter.get(); // Expected result: 1
Updating State
States allow you to update their data and send updated data to their subscribers, effects, and any connected reactive sources using set and update methods.
The set
method directly sets a new value:
const counter = state<number>(0);
counter.set(5);
The update
method computes a new value based on the current value:
const counter = state<number>(0);
counter.update(state => state + 1);
Resetting State
You can reset the state to its initial value using the reset method:
const counter = state<number>(1);
counter.reset();
Receiving Data
States can receive data from various reactive sources like emitters, states, and observables, updating their data and transmitting it to subscribers.
Methods for Receiving Data
- receive method
States can receive data from other reactive sources using the receive
method.
Without Data Transformation
import {of} from 'rxjs';
import {emitter, state} from '@bitfiber/rx';
const source1 = emitter<number>();
const source2 = state<number>(1);
const source3$ = of(1);
const receiver = state<number>(0, s => s
.receive(source1, source2, source3$));
With Data Transformation
const receiver = state<string>('', s => s
.receive(source1, value => String(value)));
- select method
Combines data from all sources, updates its data by a computed value, and emits a computed value whenever any source emits:
const receiver = state<number>(0, s => s
.select(source1, source2, source3$, (v1, v2, v3) => v1 * v2 * v3));
- zip method
Combines data from all sources, updating its data by a computed value and emitting a computed value only when all sources emit new values:
const receiver = state<number>(0, s => s
.zip(source1, source2, source3$, (v1, v2, v3) => v1 * v2 * v3));
- wait method
Waits for the first values from all sources, updates its data by a computed value, emits a computed value, and completes the stream:
const receiver = state<number>(0, s => s
.wait(source1, source2, source3$, (v1, v2, v3) => v1 * v2 * v3));
Working with Observables
States can receive data from observables, which allows you to use RxJS operators for more advanced stream manipulation before connecting them to states.
import {debounceTime, filter} from 'rxjs';
import {emitter} from '@bitfiber/rx';
const source = emitter<number>();
// The '$' property returns the emitter observable
const source$ = source.$.pipe(
filter(v => v % 2),
debounceTime(500),
);
const receiver = state<number>(0, s => s
.receive(source$));
Transmitting Data
States can transmit their updated data to other reactive sources using the transmit method.
Without Data Transformation
import {Subject} from 'rxjs';
import {emitter, state} from '@bitfiber/rx';
const source = state<number>(0, s => s
.transmit(receiver1, receiver2, receiver3));
const receiver1 = emitter<number>();
const receiver2 = state<number>(0);
const receiver3 = new Subject<number>();
With Data Transformation
import {state} from '@bitfiber/rx';
const source = state<string>(0, s => s
.transmit(receiver, (value, state) => state + Number(value)));
const receiver = state<number>(0);
Creating Side Effects
States support creating side effects using tap and effect methods.
Using Tap
The tap
method performs a side effect whenever the state emits a value.
import {state} from '@bitfiber/rx';
const logger = state<number>(0, s => s
.tap(data => console.log(data)));
Using Effect
The effect
method allows for complex stream management using RxJS operators.
import {debounceTime, filter} from 'rxjs';
import {state} from '@bitfiber/rx';
const itemId = state<number | null>(null, s => s
.effect(
filter(id => id !== null),
debounceTime(1000),
));
Comparison Type
States evaluate changes using a specific Comparison type.
By default, states use the equals
comparison type, which ensures data is updated only when
values are different based on deep equality checks.
You can change the comparison type for a specific state using the compareBy method:
import {state} from '@bitfiber/rx';
const data1 = state<number>(10, s => s
// Uses '===' for comparing values
.compareBy('strict'));
const data2 = state<number | string>(10, s => s
// Uses a custom function for comparing values
.compareBy((a, b) => Number(a) === Number(b)));
To globally change the default comparison type for all states in your application, use the changeDefaultComparison function:
import {changeDefaultComparison} from '@bitfiber/rx';
changeDefaultComparison('strict');
Lazy Emission
In scenarios where immediate emission is not desirable at the time of subscription, use the useLazyEmission or useLazyEmissionOnce methods.
For All Subscriptions
import {state} from '@bitfiber/rx';
const data = state<number>(0, s => s
.useLazyEmission());
For the Next Subscription
import {state} from '@bitfiber/rx';
const data = state<number>(0, s => s
.useLazyEmissionOnce()
// Will not emit a value at the time of subscription
.effect(...)
// Will emit a value at the time of subscription
.transmit(...));
Synchronizing with Data Sources
State can be synchronized with external data sources that implement the DataSource interface like localStoragePart. For this, use the connect method.
Once connected, the state automatically updates from the data source whenever the source changes, and conversely, updates the data source whenever the state value is changed. This bidirectional synchronization ensures that both the state and the data source remain in sync.
import {state, localStoragePart} from '@bitfiber/rx';
const theme = state<'dark' | 'light'>('dark', s => s
.connect(localStoragePart('theme')));
Managing All Streams
States allow you to apply RxJS operators across all their streams simultaneously using the manage method. This ensures consistent stream behavior by applying the same operators to all streams generated by the state.
import {delay, filter} from 'rxjs';
import {state} from '@bitfiber/rx';
// Creates a state with managed streams
const data = state<number>(0, s => s
// Applies operators to all streams of this state
.manage(
// Filters values to pass only odd numbers
filter(id => !!(id % 2)),
// Adds a delay to all emissions
delay(100),
));