Skip to content

roanosullivan/react-cursor

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

react-cursor

Functional state management abstraction for use with Facebook React

react-cursor is an opinionated javascript implementation of the Cursor concept first seen in Om, and inspired by functional zippers.

Cursors makes it easy for us work with deeply nested immutable values that are backed by React state. This means we can store our entire application state in a single nested immutable value, allowing completely stateless React views.

One of the React maintainers wrote:

[React is] there when you want to treat state as an implementation detail of a subcomponent. This is only because we don't have a good way of externalizing state changes, while simultaneously keeping the nature of them private.

Cursors solve this problem.

features

react-cursor offers the following benefits:

  • single mutable ref to app state
  • cursors for encapsulation and modularity
  • O(1) deep equality checks (like Om)
  • fastest possible react performance
  • Manipulate deeply nested immutable values backed by React state
  • Decouple your application state from the shape of the DOM, allowing application state to be normalized
  • Mechanically eliminates React's double setState issue.

tutorial

Given a React component with state like this:

var App = React.createClass({
    getInitialState: function () {
        return {
            "a": 10,
            "b": {
                "foo": {
                    "bar": 42,
                    "baz": ['red', 'green']
                }
            }
        };
    },
    render: function () {
        return <pre>{JSON.stringify(this.state, undefined, 2)}</pre>;
    }
});

Construct a cursor:

var Cursor = require('path/to/react-cursor').Cursor;

var cursor = Cursor.build(this) // `this` is the React component's this pointer
                                // or the return value of React.renderComponent

Cursors have refine, value and expose methods for all commands in React.addons.update:

  • push(array) all the items in array on the target.
  • unshift(array) all the items in array on the target.
  • splice(array of arrays) for each item in array() call splice() on the target with the parameters provided by the item.
  • set(any) replace the target entirely.
  • merge(object) merge the keys of object with the target.
  • apply(function) passes in the current value to the function and updates it with the new returned value.

Example:

cursor.refine('a').value            //=> 10
cursor.refine('a').set(11);
cursor.refine('b').refine('foo').value      //=> { 'bar': 42, 'baz': ['red', 'green'] }
cursor.refine('b').refine('foo').set({ 'bar': 43, 'baz': ['red', 'green'] })
cursor.refine('b', 'foo', 'baz', 1).set('blue')

Cursors are heavily memoized to preserve reference equality between equivalent cursors, such that we can implement React.shouldComponentUpdate trivially and O(1):

shouldComponentUpdate: function (nextProps, nextState) {
    return this.props.cursor !== nextProps.cursor;
}

Since the whole point of using cursors is to allow us to store all the app state in a single value, React needs to re-render the entire app from the top with every state change. This means that providing proper implementation of shouldComponentUpdate is critical to maintain smooth performance. react-cursor provides this optimization as a mixin which can be used like so:

var myClass = React.createClass({
    mixins: [ImmutableOptimizations(['cursor'])],
});

Props listed in refFields will compare old and new with a reference check, and other props will be compared with a value check, unless they are listed in ignoreFields which is useful under rare circumstances.

Cursors also have pendingValue() for use in event handlers. This mechanically solves the double setState bug.

example app

Cursors make it trivial to implement a React JSON editor:

live demo

Comparisons to similar libraries

There exist several similar libraries (most notably Cortex) that tackle exactly the same problem. react-cursor has one distinguishing feature: the ability to trivially implement a correct shouldComponentUpdate. Note that to do this correctly, not only do equivalent values at equal paths need to be ===, but set handlers at equal paths also need to be ===. (If the path changes, the DOM event handlers may need to be updated as well, requiring a render.)

I also believe react-cursor is the only library that attempts to address React's double setState issue.

Contributors

The initial prototypes and thought work for react-cursor was pair programmed by Dustin Getz and Daniel Miladinov.

License

react-cursor is governed under the MIT License.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 100.0%