@hungry-egg/rx-state
TypeScript icon, indicating that this package has built-in type declarations

1.0.3 • Public • Published

@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

Readme

Keywords

none

Package Sidebar

Install

npm i @hungry-egg/rx-state

Weekly Downloads

73

Version

1.0.3

License

MIT

Unpacked Size

132 kB

Total Files

13

Last publish

Collaborators

  • markevans