JSPM

  • Created
  • Published
  • Downloads 71
  • Score
    100M100P100Q45139F
  • License MIT

Unobtrusive reactive library that keeps views automatically in sync with data.

Package Exports

  • mobservable

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (mobservable) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

mobservable

Unobtrusive reactive library that keeps views automatically in sync with data.

Build Status Coverage Status mobservable channel on slack

API documentation - Typings

Philosophy

Mobservable is light-weight standalone library to create reactive primitives, functions, arrays and objects. The goal of mobservable is simple:

  1. Write simple views. Views should be subscription free.
  2. Write simple controllers and stores. Change data without thinking about how this should be reflected in views.
  3. Allow flexible model design, be able to use objects, arrays, classes, real references, and cyclic data structures in your app.
  4. Performance: find the absolute minimum amount of changes that are needed to update views.
  5. Views should be updated atomically and sychronously without showing stale or intermediate values.

Mobservable is born as part of an enterprise scale visual editor, which needs high performance rendering and covers over 400 different domain concepts. So the best performance and the simplest possible controller and view code are both of the utmost importance. See this blog for more details about that journey. Mobservable applies reactive programming behind the scenes and is inspired by MVVM frameworks like knockout and ember, yet less obtrusive to use.

The essentials

Mobservable can be summarized in two functions that will fundamentally simplify the way you write Reactjs applications. Lets take a look at this really really simple timer application:

var timerData = {
  secondsPassed: 0
};

setInterval(function() {
  this.timerData.secondsPassed++;
}, 1000);

var Timer = React.createClass({
  render: function() {
    return (<span>Seconds passed: { this.props.timerData.secondsPassed } </span> )
  }
});

React.render(<Timer timerData={timerData} />, document.body);

So what will this app do? It does nothing! The timer increases every second, but the UI never responds to that. After the interval updates the timer we should force the UI to update. But that is the kind of dependency we want to avoid in our code. So let's apply two simple functions of mobservable instead to fix this issue:

mobservable.makeReactive

The first function is makeReactive. It is the swiss knife of mobservable and turns any data structure and function into its reactive counterpart. Objects, arrays, functions; they can all be made reactive. Reactiveness is contagious; new data that is put in reactive data will become reactive as well. To make our timer reactive, just change the first three lines of the code:

var timerData = mobservable.makeReactive({
  secondsPassed: 0
});

mobservable.reactiveComponent

The second important function is reactiveComponent. It turns a Reactjs component into a reactive one, that responds automatically to changes in data that is used by its render method. It can be used to wrap any react component, either created by using ES6 classes or createClass. So to fix the example, just update the timer definition to:

var Timer = mobservable.reactiveComponent(React.createClass{
  /** Omitted */
}));

Thats all folks! Its as simple as that. The Timer will now automatically update each time timerData.secondsPassed is altered. The actual interesting thing about these changes are the things that are not in the code:

  • The setInterval method didn't alter. It still threads timerData as a plain JS object.
  • There is no state. Timer is still a dump component.
  • There is no magic context being passed through components.
  • There are no subscriptions of any kind that need to be managed.
  • There is no higher order component that needs configuration; no scopes, lenses or cursors.
  • There is no forced UI update in our 'controller'.
  • If the Timer component would be somewhere deep in our app; only the Timer would be re-rendered. Nothing else.

All this missing code... it will scale well into large code-bases! It does not only work for plain objects, but also for arrays, functions, classes, deeply nested structures.

A Todo application

The following simple todo application can be found up & running on https://mweststrate.github.io/mobservable. A full TodoMVC implementation can be found here Note how the array, function and primitive of todoStore will all become reactive. There are just three calls to mobservable and all the components are kept in sync with the todoStore.

var todoStore = mobservable.makeReactive({
    todos: [
        {
            title: 'Find a clean mug',
            completed: true
        },
        {
            title: 'Make coffee',
            completed: false
        }
    ],
    completedCount: function() {
        return this.todos.filter((todo) => todo.completed).length;
    },
    pending: 0
});

todoStore.addTodo = function(title) {
    this.todos.push({
        title: title,
        completed: false
    });
};

todoStore.removeTodo = function(todo) {
    this.todos.splice(this.todos.indexOf(todo), 1);
};

todoStore.loadTodosAsync = function() {
    this.pending++;
    setTimeout(function() {
        this.addTodo('Asynchronously created todo');
        this.pending--;
    }.bind(this), 2000);
};

var TodoList = mobservable.reactiveComponent(React.createClass({
    render: function() {
        var store = this.props.store;
        return (<div>
            <ul>
                { store.todos.map((todo, idx) =>
                    (<TodoView store={ store } todo={ todo } key={ idx } />)
                ) }
                { store.pending ? (<li>Loading more items...</li>) : null }
            </ul>
            <hr/>
            Completed { store.completedCount } of { store.todos.length } items.<br/>
            <button onClick={ this.onNewTodo }>New Todo</button>
            <button onClick={ this.loadMore }>Load more...</button>
        </div>);
    },

    onNewTodo: function() {
        this.props.store.addTodo(prompt('Enter a new todo:', 'Try mobservable at home!'));
    },

    loadMore: function() {
        this.props.store.loadTodosAsync();
    }
}));

var TodoView = mobservable.reactiveComponent(React.createClass({
    render: function() {
        var todo = this.props.todo;
        return (<li>
            <input type='checkbox' checked={ todo.completed } onChange={ this.onToggleCompleted } />
            {todo.title}{' '}
            <a href='#' onClick={ this.onEdit }>[edit]</a>
            <a href='#' onClick={ this.onRemove }>[remove]</a>
        </li>);
    },

    onToggleCompleted: function() {
        this.props.todo.completed = !this.props.todo.completed;
    },

    onEdit: function(e) {
        e.preventDefault();
        this.props.todo.title = prompt('Todo:', this.props.todo.title);
    },

    onRemove: function(e) {
        e.preventDefault();
        this.props.store.removeTodo(this.props.todo);
    }
}));

React.render(<TodoList store={todoStore} />, document.getElementById('approot'));

Getting started

Either:

Examples

Read more

Runtime behavior

  • Reactive views always update synchronously (unless transaction is used)
  • Reactive views always update atomically, intermediate values will never be visible.
  • Reactive functions evaluate lazily and are not processed if they aren't observed.
  • Dependency detection is based on actual values to real-time minify the amount of dependencies.
  • Cycles are detected automatically.
  • Exceptions during computations are propagated to consumers.

FAQ

Is mobservable a framework?

Mobservabe is not a framework. It does not tell you how to structure your code, where to store state or how to process events. Yet it might free you from frameworks that poses all kinds of restrictions on your code in the name of performance.

Can I combine flux with mobservable?

Flux implementations that do not work on the assumption that the data in their stores is immutable should work well with mobservable. However, the need for flux is less when using mobservable. Mobservable already optimizes rendering and since it works with most kinds of data, including cycles and classes. So other programming paradigms like classic MVC are now can be easily applied in applications that combine ReactJS with mobservable.

Can I use mobservable together with framework X?

Probably. Mobservable is framework agnostic and can be applied in any JS environment. It just ships with a small function to transform Reactjs components into reactive view functions for convenience. Mobservable works just as well server side, and is already combined with JQuery (see this Fiddle) and Deku.