Functional state management abstraction for use with Facebook React
react-cursor hello-world in a fiddle
react-cursor
achieves most of what ImmutableJS achieves but simpler and lighter. It helps you write stateless React components, and achieve optimizied React rendering.
- Decouple your application state from the shape of the DOM, allowing application state to be normalized
- easy shouldComponentUpdate implemented using reference equality
- Designed for recursive or deeply nested data
Cursors do not make an app stateless, but they let an app keep all its state in a single place at the root -- thus the root is stateful, and all downtree views are stateless. So cursors are a tool for reducing the surface area of code that is stateful.
react-cursor uses regular javascript datastructures, and leans on React's Immutability Helpers (sugared by update-in) to provide efficient immutable operations and structure sharing. If you are already using immutable datastructures (like ImmutableJS), react-cursor is not for you.
master is stable, there is a full test suite. See package.json for latest published snapshot and npm install it with explicit version. 2.0 is fully backwards compatible with 1.x.
Cursor
has these methods:
- value() - return the value in the cursor (cursors have value semantics, for compatibility with React lifecycle methods)
- refine(key, ...keys) - return a new cursor nested inside the root cursor at some path
- swap(f) - apply f with the refined value and puts returned value into the backing store
Cursor has non-core convienence methods:
- set(v), merge(v), assoc(k, v), dissoc(k), push(xs), unshift(xs), splice([[splices]]) - convenience wrappers for swap for common updates
There is a mixin ImmutableOptimizations()
to achieve optimized rendering (yes, we also work with PureRenderMixin if you prefer that). See examples/helloworld for usage.
There are two constructors
- build(reactCmpReference) - construct cursor backed by react state
- build(rootValue, rootSwap) - construct cursor backed by state stored somewhere else
var Store = React.createClass({
getInitialState () { return {a: {b: 0}}; },
render () {
var cur = Cursor.build(this);
return <App cursor={cur} />;
}
});
React.render(<Store />, domEl);
This is the most common use case and the easiest migration path if you're already using react component local state and just want to hoist all your state to the root and not change any other code.
See js-atom. Works great in large hybrid legacy apps where state is shared with non-react bits of application.
var store = atom.createAtom({ a: { b: 0 } });
function render(key, ref, prevVal, curVal) {
var cur = Cursor.build(curVal, store.swap);
React.render(<App cursor={cur} />, domEl);
}
store.addWatch('react-renderer', render);
render('react-renderer', store, undefined, store.deref()); // Render the first time
store.removeWatch('react-renderer');
This works great in legacy page based apps that aren't pure React, since we can start and stop react rendering without losing state. This is important when our app uses more than one rendering technology. Our state is decoupled from React, so different parts of app can be coded differently but still share application state. For example, rails with TurboLinks where only one of the pages renders with React and the rest use Rails views.
This is like using an atom but dumber - just a thought experiment to demonstrate the interface. Don't do this.
var store = { a: { b: 0 } }; // store is just a var
function swap (f) {
var prev = store;
store = f(prev);
render(prev, store); // render if the var changes
}
function render(prevVal, curVal) {
var cur = Cursor.build(curVal, swap);
React.render(<App cursor={cur} />, domEl);
}
render(undefined, store); // Render the first time
Cursors are a recursive data structure, so we can implement recursive things like a JSON editor:
Here is the live demo (includes webpack sourcemaps), and source code to the live demo.
- don't mutate values that come out of a cursor, treat the values as immutable and use the cursor interface for updates
- only store associative data structures in cursors (arrays, maps, values) - no functions (see issue #19)
react-cursor
is governed under the MIT License.