• Stars
    star
    514
  • Rank 83,110 (Top 2 %)
  • Language
    JavaScript
  • License
    Apache License 2.0
  • Created almost 9 years ago
  • Updated almost 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Functional Reactive State for JavaScript and TypeScript

DerivableJS

State made simple → Effects made easy

npm Build Status Coverage Status Join the chat at https://gitter.im/ds300/derivablejs Empowered by Futurice's open source sponsorship program .min.gz size

Derivables are an Observable-like state container with superpowers. Think MobX distilled to a potent essence, served with two heaped tablespoons of extra performance, a garnish of declarative effects management, and a healthy side-salad of immutability.

Quick start

There are two types of Derivable:

  • Atoms

    Atoms are simple mutable references to immutable values. They represent the ground truth from which all else is derived.

    import { atom } from "derivable";
    
    const name = atom("Richard");
    
    name.get(); // => 'Richard'
    
    name.set("William");
    
    name.get(); // => 'William'
  • Derivations

    Derivations are declarative transformations of values held in atoms. You can create them with the derive function.

    import { derive } from "derivable";
    
    const cyber = word =>
      word
        .toUpperCase()
        .split("")
        .join(" ");
    
    const cyberName = derive(() => cyber(name.get()));
    
    cyberName.get(); // 'W I L L I A M'
    
    name.set("Sarah");
    
    cyberName.get(); // 'S A R A H'

    Unlike atoms, derivations cannot be modified in-place with a .set method. Their values change only when one or more of the values that they depend upon change. Here is an example with two dependencies.

    const transformer = atom(cyber);
    
    const transformedName = derive(() => transformer.get()(name.get()));
    
    transformedName.get(); // => 'S A R A H'
    
    const reverse = string =>
      string
        .split("")
        .reverse()
        .join("");
    
    transformer.set(reverse);
    
    transformedName.get(); // => 'haraS'
    
    name.set("Fabian");
    
    transformedName.get(); // => 'naibaF'

    derive takes a function of zero arguments which should dereference one or more Derivables to compute the new derived value. DerivableJS then sneakily monitors who is dereferencing who to infer the parent-child relationships.

Reactors

Declarative state management is nice in and of itself, but the real benefits come from how it enables us to more effectively manage side effects. DerivableJS has a really nice story on this front: changes in atoms or derivations can be monitored by things called Reactors, which do not themselves have any kind of 'current value', but are more like independent agents which exist solely for executing side effects.

Let's have a look at a tiny example app which greets the user:

import { atom, derive, transact } from "derivable";

// global application state
const name = atom("World"); // the name of the user
const countryCode = atom("en"); // for i18n

// static constants don't need to be wrapped
const greetings = {
  en: "Hello",
  de: "Hallo",
  es: "Hola",
  cn: "您好",
  fr: "Bonjour"
};

// derive a greeting message based on the user's name and country.
const greeting = derive(() => greetings[countryCode.get()]);
const message = derive(() => `${greeting.get()}, ${name.get()}!`);

// set up a Reactor to print the message every time it changes, as long as
// we know how to greet people in the current country.
message.react(msg => console.log(msg), { when: greeting });
// $> Hello, World!

countryCode.set("de");
// $> Hallo, World!
name.set("Dagmar");
// $> Hallo, Dagmar!

// we can avoid unwanted intermediate reactions by using transactions
transact(() => {
  countryCode.set("fr");
  name.set("Étienne");
});
// $> Bonjour, Étienne!

// if we set the country code to a country whose greeting we don't know,
// `greeting` becomes undefined, so the `message` reactor won't run
// In fact, the value of `message` won't even be recomputed.
countryCode.set("dk");
// ... crickets chirping

The structure of this example can be depicted as the following DAG:

Key differences with MobX

  • Smaller API surface area.

    There are far fewer kinds of thing in DerivableJS, and therefore fewer things to learn and fewer surprising exceptions and spooky corners. This reduces noise and enhances one's ability to grok the concepts and wield the tools on offer. It also shrinks the set of tools on offer, but maybe that's not a bad thing:

    It seems that perfection is attained not when there is nothing more to add, but when there is nothing more to remove.

    - Antoie de Saint Exupéry

  • No transparent dereferencing and assignment.

    It is always necessary to call .get on derivables to find out what's inside, and you always have to call .set on atoms to change what's inside. This provides a consistent semantic and visual distinction between ordinary values and derivable values.

  • No observable map and array types.

    So you probably have to use something extra like Immutable, icepick or pure javascript immutable arrays to deal with collections. Not great if you're just out to get shit done fast, but the benefits of immutable collections become more and more valuable as projects mature and grow in scope.

  • More subtle control over reactors

    DerivableJS has a tidy and flexible declarative system for defining when reactors should start and stop. This is rather nice to use for managing many kinds of side effects.

  • Speed

    DerivableJS is finely tuned, and propagates change significantly faster than MobX. [link to benchmark-results.html forthcoming]

API / Documentation

Over Here

Usage

DerivableJS is fairly mature, and has been used enough in production by various people to be considered a solid beta-quality piece of kit.

With React

The fantastic project react-derivable lets you use derivables in your render method, providing seamless interop with component-local state and props.

With Immutable

DerivableJS works spiffingly with Immutable, which is practically required if your app deals with medium-to-large collections.

Debugging

Due to inversion of control, the stack traces you get when your derivations throw errors can be totally unhelpful. There is a nice way to solve this problem for dev time. See setDebugMode for more info.

Examples

See here

Contributing

I heartily welcome questions, feature requests, bug reports, and general suggestions/criticism on the github issue tracker. I also welcome bugfixes via pull request (please read CONTRIBUTING.md before sumbmitting).

Inspiration <3

License

Copyright 2015 David Sheldrick <[email protected]>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

More Repositories

1

patch-package

Fix broken node modules instantly 🏃🏽‍♀️💨
TypeScript
9,835
star
2

react-native-typescript-transformer

Seamlessly use TypeScript with React Native
JavaScript
658
star
3

lazyrepo

Caching task runner for npm/pnpm/yarn monorepos.
TypeScript
519
star
4

jetzt

Speed reader extension for chrome
JavaScript
483
star
5

postinstall-postinstall

JavaScript
48
star
6

android-capture

Capture video and screenshots from Android devices and emulators.
JavaScript
32
star
7

code-in-motion

JavaScript
17
star
8

cfg

Option management for Clojure
Clojure
11
star
9

ddom

derivable dom
JavaScript
11
star
10

redux-mobx-connect

A simple alternative to react-redux
TypeScript
9
star
11

gudetama

Make your CI scripts as lazy as possible
TypeScript
7
star
12

prismic-react

Compiling Prismic.io content to ReactElements
JavaScript
5
star
13

lunch-time

TypeScript
5
star
14

apt

Anchored Packed Trees for compositional distributional semantics
Java
3
star
15

artsy-nonlocality

Chrome extension for artsy employees to launch zoom meetings more easily
TypeScript
3
star
16

patch-package-website-design

figma file for unused patch-package website design
3
star
17

derivables-talk-demo

Code demonstration of Derivables from my talk
JavaScript
2
star
18

rn-back

dual n-back in react native
JavaScript
2
star
19

postinstall-prepare

JavaScript
2
star
20

kfs

TypeScript
2
star
21

encrypt-and-decrypt

TypeScript
2
star
22

atom-gitnav

move forward and backward through git history with hotkeys
CoffeeScript
1
star
23

mental

super simple mental arithmetic game
JavaScript
1
star
24

loggy

dumb irc room logger
Go
1
star
25

sicp

going through sicp in emacs
Scheme
1
star
26

norm

A suite of tools coalescing into a lexical normalisation system for tweets.
Clojure
1
star
27

TestCustomSourceExts

Objective-C
1
star
28

apt-py

python api for reading and manipulating APTs
Python
1
star
29

vimes

oh lisp
TypeScript
1
star
30

nlp-trie

Immutable trie implementation for Clojure. Uses string keys. Optimised for lookups and traversal.
Clojure
1
star
31

gridster

JavaScript
1
star
32

FlowDoge

wow such flow
TypeScript
1
star