reactor
A lightweight reactive programming library for javascript that includes automatic dependency tracking for computed values.

Changelog
Installation
Installation can be accomplished using npm:
npm install @residualeffect/reactor
Documentation
Observables
A basic observable is the foundation of this library.
import { Observable } from "@residualeffect/reactor";
const t = new Observable(3);
const unsubscribe = t.Subscribe(observerFunc);
t.Value = 5;
expect(t.Value).toStrictEqual(5);
unsubscribe();
Observable arrays
A basic observable works just fine for simple types, but when working with an array, mutations to the array require you to modify and the re-set the observable value. ObservableArrays simplify this by providing data modification capabilities that notify subscribers.
import { ObservableArray } from "@residualeffect/reactor";
const t = new ObservableArray<number>([3]);
const unsubscribe = t.Subscribe(observerFunc);
t.push(5);
expect(t.Value).toStrictEqual([3, 5]);
t.Value = [6];
expect(t.Value).toStrictEqual([6]);
unsubscribe();
Observable objects
When using an observable for a complex object, you need to be careful to prevent unexpected modification to properties within the object. ObservableObject can help this by carefully controlling when modifications are allowed and making sure observers are appropriately notified.
import { ObservableObject } from "@residualeffect/reactor";
const t = new ObservableObject<SomeType>({ PropertyA: "Hello", PropertyB: "World" });
const unsubscribe = t.Subscribe(observerFunc);
expect(t.Value).toStrictEqual({ PropertyA: "Hello", PropertyB: "World" });
t.Value = { PropertyA: "Testing", PropertyB: "A Lot" };
expect(t.Value).toStrictEqual({ PropertyA: "Testing", PropertyB: "A Lot" });
t.Update(x => { x.PropertyA = "Tricky"; });
expect(t.Value).toStrictEqual({ PropertyA: "Tricky", PropertyB: "A Lot" });
unsubscribe();
Computed Observables
Computed observables are values that are automatically generated when other observable values change. This library includes automatic dependency tracking for the computed observables you define.
import { Observable, Computed } from "@residualeffect/reactor";
const t = new Observable(3);
const c = new Computed(() => t.Value * 2);
const unsubscribe = c.Subscribe(observerFunc);
expect(c.Value).toStrictEqual(6);
t.Value = 5;
expect(c.Value).toStrictEqual(10);
unsubscribe();
Filtering Updates to Observables
Sometimes it is useful to filter updates to observables - for example, by limiting the rate at which the observable will modify and notify subscribers.
This library includes the ability to limit the rate of updates using a FilteredObservable with two modes of operation: Debounce, and Throttle. Debounce will notify subscribers x milliseconds after the last change, whereas Throttle will notify subscribers at most x milliseconds after the first change that was made to the value since the last notification.
import { FilteredObservable, RateLimiter, RateLimitType } from "@residualeffect/reactor";
const r = new FilteredObservable(3, RateLimiter(RateLimitType.Debounce, 200));
const unsubscribe = r.Subscribe(observerFunc);
expect(r.Value).toStrictEqual(3);
r.Value = 5;
expect(r.Value).toStrictEqual(3);
jest.advanceTimersByTime(200);
expect(r.Value).toStrictEqual(5);
r.Value = 6;
r.Value = 7;
expect(r.Value).toStrictEqual(5);
jest.advanceTimersByTime(200);
expect(r.Value).toStrictEqual(7);
unsubscribe();
Read-Only Observables
Observables of various types can all be converted in to a ReadOnlyObservable, which provides only read-only access to the observable value and the ability to subscribe, but no other capabilities. These are helpful when you need to be able to interchangeably use different types of observables for a function call.
import { Observable, Computed, ReadOnlyObservable } from "@residualeffect/reactor";
const t = new Observable(3);
const c = new Computed(() => t.Value * 2);
const rt = t.AsReadOnly();
const rc = c.AsReadOnly();
function IsBig(input: ReadOnlyObservable<number>): boolean {
return input.Value > 5;
}
expect(IsBig(rt)).toStrictEqual(false);
expect(IsBig(rc)).toStrictEqual(true);
const r = t.AsReadOnly();
Custom equality comparison
By default, reactor will do it's best to quickly determine if a value has changed before notifying subscribers. The default implementation is primitive, and tends to over-detect changes rather than under-detect them -- especially when working with arrays and objects.
In some cases it may make sense to provide a custom equality comparison function to reactor, so that it can more intelligently notify subscribers of changes.
import { Observable } from "@residualeffect/reactor";
const t = new Observable({ id: 3 }, (a, b) => a.id === b.id);
const unsubscribe = t.Subscribe(observerFunc);
t.Value = { id: 3 };
t.Value = { id: 4 };
unsubscribe();
Using Reactor with React
This library works well with react hooks (available starting with React 16.8), and can facilitate implementing fully functional, reactive application logic separately from your UI components.
Take advantage of @residualeffect/rereactor or create your own!
License
reactor is freely distributable under the terms of the MIT License.