@hungry-egg/rx-state
v1.0.3
Published
Rx state utils - helpers for managing state, based on RxJS
Downloads
304
Readme
@hungry-egg/rx-state
State management utilities based on RxJS
This library provides a very thin wrapper around some RxJS objects that are useful for state management.
There are only two main components: atom
and combine
.
All functions are importable in the usual way:
import { atom } from "@hungry-egg/rx-state";
atom
This is very simply a container for any value that changes. It's a thin wrapper around an RxJS BehaviorSubject
.
Where you'd normally have a static value
const name = "Jerry";
you simply wrap with atom
const name$ = atom("Jerry");
The $
at the end is a convention sometimes used to indicate an observable object.
Now you can subscribe to updates with a callback
const sub = name$.subscribe((name) => console.log(`Name is now ${name}`));
so that when it's changed the callback is called
const name$.set("Tom"); // Logs "Name is now Tom"
To avoid memory leaks you should unsubscribe when finished
sub.unsubscribe();
The atom
function returns a WritableAtom
, which has the following methods:
const $name = atom("Jerry");
$name.get() // "Jerry"
$name.set("Tom") // sets value to "Tom" and notifies subscribers
$name.update(n => n + " Mander") // set to "Jerry Mander" -
// use instead of set if you want to use the previous value
const uppercaseName$ = $name.pipe( // pipe can be used as per usual in RxJS,
map(n => n.toUpperCase()), // and returns an RxJS observable
...
)
const lowercaseName$ = $name.map(n => n.toLowerCase()) // "map" is provided for convenience so you
// don't need pipe, and returns a ReadonlyAtom
const $nameRO = $name.readonly() // returns a read-only version of the atom (ReadonlyAtom)
$nameRO.get() // "Jerry Mander"
$nameRO.set(...) // ERROR - METHOD DOESN'T EXIST!
$name.destroy() // rarely used but can use to remove all subscribers
The ReadonlyAtom
is similar but only has get
, map
, pipe
and subscribe
.
readonlyAtom
This is a convenience method for creating a read-only atom, that also yields a setter function.
const [count$, setCount] = readonlyAtom(4);
count$.get(); // 4 - this is just a ReadonlyAtom
setCount(7);
count$.get(); // 7
This would be useful for e.g. using in a class, where the read-only atom is public, but the setter is private:
class Person {
public name$: ReadonlyAtom<string>;
private setName: (name: string) => void;
constructor(initialName: string) {
[this.name$, this.setName] = readonlyAtom(initialName); // NOTE the parentheses when doing this
}
//... use this.setName("...") internally
}
const person = new Person("Fred");
person.name$.set("Bubba"); // ERROR: name$ is readonly so has no 'set'
combine
This combines RxJS observables or atoms in a way that is useful for efficiently using derived values.
If:
- the
atom
is analogous to a Redux store (or part of),
then
combine
is analogous to memoized selectors like Reselect.
Given multiple atoms (or other synchronous RxJS observables)
const names$ = atom(["Geoffrey", "Bungle", "George", "Zippy"]);
const selectedIndex$ = atom(1);
Then you can combine them into a new observable with a tuple
const selectedName$ = combine(
[names$, selectedIndex$], // tuple of multiple observables
([names, index]) => names[index] // calculate new value derived from values from observables
);
selectedName$.get(); // "Bungle"
or with an object
const selectedName$ = combine(
{ names: names$, idx: selectedIndex$ }, // object lookup of multiple observables
({ names, idx }) => names[idx] // calculate derived value
);
selectedName$.get(); // "Bungle"
The new observable is efficient in that
- it only makes the calculation (the 2nd argument function) once when any of its input observables have changed
- it doesn't make the calculation if no-one is subscribing
In both forms you can also call combine
with no 2nd argument
const selectedName$ = combine({ names: names$, idx: selectedIndex$ }); // = an observable that emits {names: string[], idx: number} objects
const selectedName$ = combine([names$, selectedIndex$]); // = an observable that emits [string[], number] objects
get
This library provides a convenience method for synchronously getting the value from an RxJS observable
get(count$); // 7
This only works in cases where it's able to give its current value synchronously either because
- it calls subscription callbacks synchronously, or
- it's a
BehaviorSubject
Otherwise it will throw an error
const click$ = fromEvent(document, "click");
get(click$); // THROWS AN ERROR -
// it doesn't make sense here as click$ is asynchronous
Build
yarn build
Build on file change
yarn watch
Test
yarn test