Changelog
0.12.0
Derivable#derive
does more stuff (destructuring, property/index lookup, regex matching)Derivable#react
now provides options for declarative lifecycle control.Prior to this release, the lifecycles of Reactors were controlled imperatively using theReactor#start
and Reactor#stop
methods. This was somewhat verbose and, more importantly, went against the grain of this whole declarative/reactive thing we have going here.
Now there is a better way: providing a lifetime configuration object as a second argument to the .react
method. Here are the options:
interface Lifecycle {
from?: (() => boolean) | Derivable<boolean>;
when?: (() => boolean) | Derivable<boolean>;
until?: (() => boolean) | Derivable<boolean>;
skipFirst?: boolean;
once?: boolean;
onStart?: () => void;
onStop?: () => void;
}
from
determines this initialization time of the reactor. i.e. when the given derivable becomes truthy, the reactor is initialized.
when
causes .start
and .stop
to be called when the given derivable becomes truthy and falsey respectively (but not before the reactor has been initialized, and not after it has been killed).
until
causes the reactor to be killed when the given derivable becomes truthy.
skipFirst
causes the first invocation of the reactor (after it has been initialized, and when when
is truthy) to be ignored. This is typically used if the state of the world at the time of declaration is such that invoking the reactor would be redundant or harmful.
once
causes the reactor to be killed immediately following its first invocation.
onStart
and onStop
are the same lifecycle hooks that were previously provided.
Example usage:
const n = atom(0);
n.react(n => console.log(`n is ${n}`), {
from: () => n.get() > 0, // start when n > 0
when: () => n.get() %2 === 0, // only react when n is even
until: () => n.get() => 5 // stop when n >= 5
});
// ... no output
n.set(1);
// ... no output (n is odd)
n.set(2);
// $> n is 2
n.set(3);
// ... no output (n is odd)
n.set(4);
// $> n is 4
n.set(5);
// ... no output (reactor was killed)
n.set(4);
// ... no output (reactors don't come back from the dead)
Derivable#derive
new capabilitiesRegExp matching
const string = atom('hello world');
const firstLetters = string.derive(/\b\w/g);
firstLetters.get();
// => ['h', 'w']
Property/Index lookup
const obj = atom({foo: 'FOO!'});
const foo = obj.derive('foo');
foo.get();
// => 'FOO!'
const arr = atom(['one', 'two']);
const first = arr.derive(0);
first.get();
// => 'one'
Destructuring
const string = atom('hello world')
const [len, upper, firstChar, words] = string.derive([
'length', s => s.toUpperCase(), 0, /\w+/g
]);
Also note that these work with derivable versions of the arguments:
const arr = atom(['one', 'two', 'three']);
const idx = atom(0);
const item = arr.derive(idx);
item.get();
// => 'one'
idx.set(1);
item.get();
// => 'two'
Previously 'lensed atoms' could only have one underlying atom. It is now possible to lens over an arbitrary number of atoms using the new CompositeLens
interface:
type CompositeLens<T> = {
// no-arg getter uses lexical closure to deref and combine atoms
get: () => T,
// one-arg setter to tease apart the value being set and push it
// up to the atoms manually
// runs in an implicit transaction.
set: (value: T) => void
}
Instances of which may be passed to a new 1-arity version of the top-level lens
function to create lensed atoms:
const $FirstName = atom('John');
const $LastName = atom('Steinbeck');
const $Name = lens({
get: () => $FirstName.get() + ' ' + $LastName.get(),
set: (val) => {
const [first, last] = val.split(' ');
$FirstName.set(first);
$LastName.set(last);
}
});
$Name.get(); // => 'John Steinbeck'
$Name.set('James Joyce').
$LastName.get(); // => 'Joyce'
Because JS has no standard way to override equality comparisons, DerivableJS makes it possible to inject equality-checking logic at the module level using the top-level function withEquality
which returns a new instance of DerivableJS using the given equality-checking function.
It is now also possible to do this on a per-derivable basis.
The new Derivable#withEquality
method creates a clone of a derivable, which new derivable uses the given equality-checking function. It looks like this:
import { equals } from 'ramda'
const $Person = atom({name: "Steve"}).withEquality(equals);
$Person.react(({name}) => console.log(`name is ${name}`));
// $> name is Steve
$Person.set({name: "Steve"});
// ... no output (this would print the name again
// if using DerivableJS's standard equality function
// which only does strict-equality (===) checks if no .equals
// method is present on the arguments being compared)
atomic
/atomically
These new top-level functions are identical to transaction
/transact
respectively except that they do not create new (nested) transactions if already in a transaction. This is almost always the desired behaviour, unless you want to gracefully abort transactions.
Derivable#react
method:You can get the old behaviour by converting
d.react(f);
to
d.reactor(f).start().force();
Although it is recommended to switch to using the new declarative lifecycle stuffs if possible.
Derivable#reactWhen
method was removedUse $d.react(r, {when: $when})
.
That was a silly idea...
derive
(except the tagged template string version, that's still there). Use the Derivable#derive
method instead.mDerive
. Use the Derivable#mDerive
method instead.lens
. Use the Derivable#lens
method instead.lookup
. Use the Derivable#derive(string|number)
method instead.destruct
. Use the Derivable#derive([string|number])
method instead.ifThenElse
. Use the Derivable#then
method instead.mIfThenElse
. Use the Derivable#mThen
method instead.not
. Use the Derivable#not
method instead.switchCase
. Use the Derivable#switch
method instead.get
. Use the Derivable#get
method instead.set
. Use the Atom#set
method instead.swap
. Use the Atom#swap
method instead.Changelog
0.11.0
Derivable#reactWhen
methodA very common pattern I've needed in my use of DerivableJS has been starting and stopping some reactor based on the value of some piece of state. Dependent Reactors were implemented for this very reason, and it seems to be an elegant way to express many kinds of effectful logic.
This new method enables one to avoid giving the dependent reactor a lexical binding. e.g. before you would do this:
const r = $thing.reactor(doEffects);
$condition.react(cond => {
if (cond) r.start.force();
else r.stop();
};
now you can just write:
$thing.reactWhen($condition, doEffects);
lemon squeezy
Changelog
0.10.0
Due to inversion of control, the stack traces you get when your derivations or reactors throw errors can be totally unhelpful. This pull request solves that issue by enabling JS Errors to be created (but not thrown) when derivations are instantiated in order to capture the stack trace at the point of instantiation. Then if a derivation throws an error, its instantiation stack trace is logged so we can easily identify exactly which derivation threw the error, and which derivations the error propagated up through.
Creating errors is quite expensive, and can cause noticeable slowdown if there are enough derivations being instantiated, so this mode can be toggled on/off for dev/prod respectively. It is off by default.
See the top-level setDebugMode function.