npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

reactive-function

v0.14.0

Published

A library for managing data flows and changing state.

Downloads

41

Readme

reactive-function

A library for managing reactive data flows.

NPM NPM Build Status

This library provides the ability to define reactive data flows by modeling application state as a directed graph and using topological sorting to compute the order in which changes should be propagated.

Table of Contents

Examples

Full Name

Suppose you have two reactive properties to represent someone's first and last name.

var firstName = ReactiveProperty("Jane");
var lastName = ReactiveProperty("Smith");

Another reactive property can represent the full name of the person.

var fullName = ReactiveProperty();

You could set the full name value like this.

fullName(firstName() + " " + lastName());

However, the above code sets the value of fullName only once. Here's how you can define a ReactiveFunction that automatically updates fullName whenever firstName or lastName change.

var reactiveFunction = ReactiveFunction({
  inputs: [firstName, lastName],
  output: fullName,
  callback: function (first, last){
    return first + " " + last;
  }
});

Whenever firstName or lastName change, the callback defined above will be executed on the next animation frame. If you don't want to wait until the next animation frame, you can force a synchronous evaluation of the data flow graph by invoking digest.

ReactiveFunction.digest();

Now you can access the computed fullName value by invoking it as a getter.

console.log(fullName()); // Prints "Jane Smith"

ABC

The output of one reactive function can be used as an input to another.

var a = ReactiveProperty(5);
var b = ReactiveProperty();
var c = ReactiveProperty();

ReactiveFunction({
  inputs: [a],
  output: b,
  callback: function (a){ return a * 2; }
});

ReactiveFunction({
  inputs: [b],
  output: c,
  callback: function (b){ return b / 2; }
});

ReactiveFunction.digest();
assert.equal(c(), 5);

Tricky Case

This is the case where Model.js fails because it uses Breadth-first Search to propagate changes. In this graph, propagation using breadth-first search would cause e to be set twice, and the first time it would be set with an inconsistent state. This fundamental flaw cropped up as flashes of inconstistent states in some interactive visualizations built on Model.js. For example, it happens when you change the X column in this Magic Heat Map. This flaw in Model.js is the main inspiration for making this library and using topological sort, which is the correct algorithm for propagating data flows and avoiding inconsistent states.

var a = ReactiveProperty(5);
var b = ReactiveProperty();
var c = ReactiveProperty();
var d = ReactiveProperty();
var e = ReactiveProperty();

ReactiveFunction({ inputs: [a],    output: b, callback: function (a){    return a * 2; } });
ReactiveFunction({ inputs: [b],    output: c, callback: function (b){    return b + 5; } });
ReactiveFunction({ inputs: [a],    output: d, callback: function (a){    return a * 3; } });
ReactiveFunction({ inputs: [c, d], output: e, callback: function (c, d){ return c + d; } });

ReactiveFunction.digest();
assert.equal(e(), ((a() * 2) + 5) + (a() * 3));

a(10);
ReactiveFunction.digest();
assert.equal(e(), ((a() * 2) + 5) + (a() * 3));

Ohm's Law

Ohm's Law is a mathematical relationship between 3 quantities in electrical circuits:

  • V, voltage. V = IR
  • I, current. I = V/R
  • R, resistance. R = V/I

Given any two of these values, one can calculate the third. Here's an example where if any two of the values are set, the third will automatically be calculated.

var I = ReactiveProperty();
var V = ReactiveProperty();
var R = ReactiveProperty();

ReactiveFunction({ output: V, inputs: [I, R], callback: function (i, r){ return i * r; } });
ReactiveFunction({ output: I, inputs: [V, R], callback: function (v, r){ return v / r; } });
ReactiveFunction({ output: R, inputs: [V, I], callback: function (v, i){ return v / i; } });

V(9)
I(2)
ReactiveFunction.digest();
console.log(R()); // Prints 4.5

R(6)
I(2)
ReactiveFunction.digest();
console.log(V()); // Prints 12

V(9);
R(18);
ReactiveFunction.digest();
console.log(I()); // Prints 0.5

For more detailed example code, have a look at the tests.

Installing

If you are using NPM, install this package with:

npm install reactive-function

Require it in your code like this:

var ReactiveFunction = require("reactive-function");

This library is designed to work with reactive-property, you'll need that too.

npm install reactive-property

var ReactiveProperty = require("reactive-property");

API Reference

Managing Reactive Functions

# ReactiveFunction(options)

Construct a new reactive function. The options argument should have the following properties.

  • inputs - The input properties. An array of ReactiveProperty instances.
  • output (optional) - The output property. An instance of ReactiveProperty.
  • callback - The reactive function callback. Arguments are values of inputs. The return value will be assigned to output.

This constructor sets up a reactive function such that callback be invoked

  • when all input properties are defined,
  • after any input properties change,
  • during a digest.

An input property is considered "defined" if it has any value other than undefined. The special value null is considered to be defined.

An input property is considered "changed" when

  • the reactive function is initially set up, and
  • whenever its value is set.

Input properties for one reactive function may also be outputs of another.

# reactiveFunction.destroy()

Cleans up resources allocated to this reactive function.

More specifically:

  • Removes listeners from inputs.
  • Removes edges from the data flow graph (from each input).
  • Removes property nodes from the data flow graph if they have no incoming or outgoing edges.

You should invoke this function when finished using reactive functions in order to avoid memory leaks.

# ReactiveFunction.link(propertyA, propertyB)

Sets up one-way data binding from propertyA to propertyB. Returns an instance of ReactiveFunction.

Arguments:

Example:

var a = ReactiveProperty(5);
var b = ReactiveProperty(10);
var link = ReactiveFunction.link(a, b);

This is equivalent to:

var a = ReactiveProperty(5);
var b = ReactiveProperty(10);
var link = ReactiveFunction({
  inputs: [a],
  output: b,
  callback: function (a){ return a; }
});

Data Flow Execution

# ReactiveFunction.digest()

Propagates changes from input properties through the data flow graph defined by all reactive properties using topological sorting. An edge in the data flow graph corresponds to a case where the output of one reactive function is used as an input to another.

Whenever any input properties for any reactive function change, digest is debounced (scheduled for invocation) on the nextFrame. Because it is debounced, multiple synchronous changes to input properties collapse into a single digest invocation.

Digests are debounced to the next animation frame rather than the next tick because browsers will render the page at most every animation frame (approximately 60 frames per second). This means that if DOM manipulations are triggered by reactive functions, and input properties are changed more frequently than 60 times per second (e.g. mouse or keyboard events), the DOM manipulations will only occur at most 60 times per second, not more than that.

# ReactiveFunction.nextFrame(callback)

Schedules the given function to execute on the next animation frame or next tick.

This is a simple polyfill for requestAnimationFrame that falls back to setTimeout. The main reason for having this is for use in the tests, which run in a Node.js environment where requestAnimationFrame is not available. Automatic digests are debounced against this function.

Serialization

# ReactiveFunction.serializeGraph()

Serializes the data flow graph. Returns an object with the following properties.

  • nodes An array of objects, each with the following properties.
    • id The node identifier string.
    • propertyName The property name for this node, derived from property.propertyName for each property.
  • links An array of objects representing edges, each with the following properties.
    • source The node identifier string of the source node (u).
    • target The node identifier string of the target node (v).

Example:

var firstName = ReactiveProperty("Jane");
var lastName = ReactiveProperty("Smith");
var fullName = ReactiveProperty();

// For serialization.
firstName.propertyName = "firstName";
lastName.propertyName = "lastName";
fullName.propertyName = "fullName";

ReactiveFunction({
  inputs: [firstName, lastName],
  output: fullName,
  callback: function (first, last){
    return first + " " + last;
  }
});

var serialized = ReactiveFunction.serializeGraph();

The value of serialized will be:

{
  "nodes": [
    { "id": "95", "propertyName": "fullName" },
    { "id": "96", "propertyName": "firstName" },
    { "id": "97", "propertyName": "lastName" }
  ],
  "links": [
    { "source": "96", "target": "95" },
    { "source": "97", "target": "95" }
  ]
}

See also:

Related Work