Reactor
Reactor is a library that allows you to write functions that automatically respond to variable changes within them. This allows you to think of your project as a single, static state instead of the constantly changing mess inherent to large projects. Stop tracking changes to your variables and start writing beautiful, intuitive code.
Installation
Node
$ npm install reactor-lib
var Reactor = require('reactor-lib');
Browser
<script src="path/to/reactor.js"></script>
Quick Start
The following example will:
- Create a Reactor with a default value
- Run a function that retrieves and logs its value
- Change the value of the Reactor which will automatically run the previous function
var my_name = new Reactor('Sammy');
Reactor(function() {
console.log(my_name());
});
my_name('Bobby');
Output:
Sammy
Bobby
Slow Start
Creating Reactors
Reactors are created using new Reactor([default_value]). They can hold values and can optionally be initialized with a default value.
var my_name = new Reactor();
var my_age = new Reactor(24);
my_name('Sammy');
console.log(my_name());
console.log(my_age());
Output:
Sammy
24
NOTE: You must use the new keyword when creating a Reactor
Creating Reactor Functions
Reactor Functions are creating using Reactor(function, [context]). This runs the function immediately. Within the passed in function, if a Reactor's value is retrieved, the function is registered as dependent on that value and will be re-run when the value changes.
Reactor Functions can contain any number of Reactors. Conversely, Reactors can be used within any number of Reactor Functions.
// This function is dependent on my_name and my_age
Reactor(function() {
var name = my_name();
var age = my_age();
console.log(name + ' ' + age);
});
// This function is also dependent on my_name and my_age
Reactor(function() {
my_age();
console.log('Hello, ' + my_name());
});
NOTE: Do not use the new keyword when creating a Reactor Function
Triggering a Reaction
Any time a Reactor's value is changed, all functions dependent on it will be re-run. This is called a reaction. A reaction only occurs if the new value fails a strict equality comparison with the previous value. A reaction can also be triggered without changing the value by calling <Reactor>.trigger().
// Implicitly trigger a reaction by setting the Reactor's value
my_name('Bobby');
// Explicity trigger a reaction by calling trigger()
my_age.trigger();
Be careful when dealing with objects and arrays. Modifying a property on an object/array is not detected as a change. If you want to force a reaction after modifying an object/array's properties, use the <Reactor>.trigger() method.
var options = {};
var my_options = new Reactor(options);
options.foo = true;
// This does not trigger a reaction because the Reactor already stores a reference to options
my_options(options);
// This triggers a reaction regardless of the value
my_options.trigger();
// This triggers a reaction because a different object is being set
my_options({foo : true});
Reactors without values
Reactors do not need to have values. In some cases, it's useful to have a Reactor whose sole purpose is to activate Reactor Functions explicitly (similar to the pub/sub pattern).
Because they hold no values, use <Reactor>.trigger() to trigger a reaction.
var generate_random_number = new Reactor();
Reactor(function() {
// Registers generate_random_number as a dependency
generate_random_number();
console.log(Math.random());
});
// Triggers a reaction
generate_random_number.trigger();
Reaction timing and order
When a reaction is triggered, it is not executed immediately. Instead, it is scheduled for the next time the client is idle (typically a few milliseconds). This is to avoid recursion and to aggregrate rapid changes to multiple Reactors in a single reaction.
For simplicity, the order in which Reactor Functions are executed in a reaction is arbitrary. This is because the order of multiple Reactor Functions with shared Reactor dependencies becomes confusing, unpredictable, and not very useful in practical situations. Your Reactor Functions should be isolated units capable of being called at any time in any order.
Examples
Linking a Reactor to an input
var name = new Reactor('Sammy');
var name_input = document.getElementById('name-input');
Reactor(function() {
name_input.value = name.value();
});
name_input.addEventListener('change', function() {
name(this.value);
});
Handlebars template
Rendering avar template = Handlebars.compile(user_info_template);
var container = document.getElementById('user-info-container');
Reactor(function() {
container.innerHTML = template({
first_name : first_name(),
last_name : last_name(),
});
});
API
Reactor(function, [context])
Runs a function with an optional context. If a Reactor's value is retrieved within the function, the function is registered as dependent on the Reactor and will be re-run if a reaction is triggered.
new Reactor([default_value])
Creates a new Reactor with an optional default value
<Reactor>()
Returns the value of a Reactor
<Reactor>(value)
Sets the value of a Reactor. If the value is not strictly equal to the previous value, a reaction is scheduled.
<Reactor>.trigger()
Triggers a reaction without setting the Reactor's value