This repository is home to baobab's React integration (from v1.0.0 and onwards).
It aims at implementing a handful of popular React patterns so that you're free to choose the one you want rather than being imposed one by the library.
Currently implemented patterns being: mixins, higher order components, ES7 decorators and wrapper components.
You can install baobab-react
through npm:
npm install baobab-react
Then require the desired pattern and only this one will be loaded (this means that your browserify/webpack bundle, for instance, won't load unnecessary files and end up bloated).
Example
var mixins = require('baobab-react/mixins');
In order to keep component definitions detached from any particular instance of Baobab, I divided the mixins, higher order components etc. into two:
- The Root aims at passing a baobab tree through context so that child component (branches) may use it. Typically, your app's top-level component will probably be a root.
- The Branches, bound to cursors, get their data from the tree given by the root.
This is necessary so that isomorphism can remain an enjoyable stroll in the park (you UI would remain a pure function).
var mixins = require('baobab-react/mixins');
With mixins, you need to pass your tree through props.
var React = require('react'),
Baobab = require('baobab'),
mixin = require('baobab-react/mixins').root;
var tree = new Baobab({
name: 'John',
surname: 'Talbot'
});
var Application = React.createClass({
mixins: [mixin],
render: function() {
return (
<div>
<OtherComponent />
</div>
);
}
});
React.render(<Application tree={tree} />, mountNode);
Binding a component to cursors
var React = require('react'),
mixin = require('baobab-react/mixins').branch;
var MyComponent = React.createClass({
mixins: [mixin],
cursors: {
name: ['name'],
surname: ['surname']
},
render: function() {
// Cursor data is passed through state
return (
<span>
Hello {this.state.name} {this.state.surname}
</span>
);
}
});
Accessing the tree or the cursors from the component
var React = require('react'),
mixin = require('baobab-react/mixins').branch;
var MyComponent = React.createClass({
mixins: [mixin],
cursors: {
name: ['name'],
surname: ['surname']
},
handleClick: function() {
// Tree available through the context
this.context.tree.emit('customEvent');
// I am not saying this is what you should do but
// anyway, if you need to access cursors:
this.cursors.name.set('Jack');
},
render: function() {
// Cursor data is passed through state
return (
<span onClick={this.handleClick}>
Hello {this.state.name} {this.state.surname}
</span>
);
}
});
import {root, branch} from 'baobab-react/higher-order';
import React, {Component} from 'react';
import Baobab from 'baobab';
import {root} from 'baobab-react/higher-order';
var tree = new Baobab({
name: 'John',
surname: 'Talbot'
});
class Application extends Component {
render() {
return (
<div>
<OtherComponent />
</div>
);
}
}
var ComposedComponent = root(Application, tree);
React.render(<ComposedComponent />, mountNode);
Bind a component to cursors
import React, {Component} from 'react';
import {branch} from 'baobab-react/higher-order';
class MyComponent extends Component {
render() {
// Cursor data is passed through props
return (
<span>
Hello {this.props.name} {this.props.surname}
</span>
);
}
}
export default branch(MyComponent, {
cursors: {
name: ['name'],
surname: ['surname']
}
});
Access the tree or the cursors from the component
You can access the tree or the cursors from the context. However, you'll have to define contextTypes
for your component if you want to be able to do so.
Some handy prop types wait for you in baobab-react/prop-types
if you need them.
import React, {Component} from 'react';
import {branch} from 'baobab-react/higher-order';
import PropTypes from 'baobab-react/prop-types';
class MyComponent extends Component {
static contextTypes = {
tree: PropTypes.baobab,
cursors: PropTypes.cursors
}
handleClick() {
// Tree available through the context
this.context.tree.emit('customEvent');
// I am not saying this is what you should do but
// anyway, if you need to access cursors:
this.context.cursors.name.set('Jack');
}
render() {
// Cursor data is passed through props
return (
<span onClick={this.handleClick.bind(this)}>
Hello {this.props.name} {this.props.surname}
</span>
);
}
}
export default branch(MyComponent, {
cursors: {
name: ['name'],
surname: ['surname']
}
});
Warning: decorators are a work-in-progress proposition for ES7 (they are pretty well handed by babel still). You have been warned!
import {root, branch} from 'baobab-react/decorators';
import React, {Component} from 'react';
import Baobab from 'baobab';
import {root} from 'baobab-react/decorators';
var tree = new Baobab({
name: 'John',
surname: 'Talbot'
});
@root(tree)
class Application extends Component {
render() {
return (
<div>
<OtherComponent />
</div>
);
}
}
React.render(<Application />, mountNode);
Bind a component to cursors
import React, {Component} from 'react';
import {branch} from 'baobab-react/decorators';
@branch({
cursors: {
name: ['name'],
surname: ['surname']
}
})
class MyComponent extends Component {
render() {
// Cursor data is passed through props
return (
<span>
Hello {this.props.name} {this.props.surname}
</span>
);
}
}
Access the tree or the cursors from the component
You can access the tree or the cursors from the context. However, you'll have to define contextTypes
for your component if you want to be able to do so.
Some handy prop types wait for you in baobab-react/prop-types
if you need them.
import React, {Component} from 'react';
import {branch} from 'baobab-react/decorators';
import PropTypes from 'baobab-react/prop-types';
@branch({
cursors: {
name: ['name'],
surname: ['surname']
}
})
class MyComponent extends Component {
static contextTypes = {
tree: PropTypes.baobab,
cursors: PropTypes.cursors
}
handleClick() {
// Tree available through the context
this.context.tree.emit('customEvent');
// I am not saying this is what you should do but
// anyway, if you need to access cursors:
this.context.cursors.name.set('Jack');
}
render() {
// Cursor data is passed through props
return (
<span onClick={this.handleClick.bind(this)}>
Hello {this.props.name} {this.props.surname}
</span>
);
}
}
import {Root, Branch} from 'baobab-react/wrappers';
import React, {Component} from 'react';
import Baobab from 'baobab';
import {Root} from 'baobab-react/wrappers';
var tree = new Baobab({
name: 'John',
surname: 'Talbot'
});
class Application extends Component {
render() {
return (
<div>
<OtherComponent />
</div>
);
}
}
React.render(
(
<Root tree={tree}>
<Application />
</Root>
),
mountNode
);
Bind a component to cursors
import React, {Component} from 'react';
import {Branch} from 'baobab-react/wrappers';
class MyComponent extends Component {
render() {
// Cursor data is passed through props
return (
<span>
Hello {this.props.name} {this.props.surname}
</span>
);
}
}
class SuperiorComponent extends Component {
render() {
return (
<Branch cursors={{
name: ['name'],
surname: ['surname']
}}>
<MyComponent />
</Branch>
);
}
}
Access the tree or the cursors from the component
import React, {Component} from 'react';
import {Branch} from 'baobab-react/wrappers';
import PropTypes from 'baobab-react/prop-types';
class MyComponent extends Component {
static contextTypes = {
tree: PropTypes.tree,
cursors: PropTypes.cursors
};
handleClick() {
// Tree available through the context
this.context.tree.emit('customEvent');
// I am not saying this is what you should do but
// anyway, if you need to access cursors:
this.context.cursors.name.set('Jack');
}
render() {
// Cursor data is passed through props
return (
<span onClick={this.handleClick.bind(this)}>
Hello {this.props.name} {this.props.surname}
</span>
);
}
}
class SuperiorComponent extends Component {
render() {
return (
<Branch cursors={{
name: ['name'],
surname: ['surname']
}}>
<MyComponent />
</Branch>
);
}
}
Each of the pattern described above can receive a cursors
mapping that will associate a key of your state/props to the value of the given cursor.
Considering the following tree:
var tree = new Baobab({
user: {
name: 'John'
},
palette: {
colors: ['blue', 'yellow']
}
});
Those mappings can be defined likewise:
Using paths
var mapping = {
cursors: {
name: ['user', 'name'],
color: ['palette', 'colors', 1]
}
};
Using cursors
var cursor = tree.select('user', 'name');
var mapping = {
cursors: {
name: cursor,
color: ['palette', 'colors', 1]
}
};
Using a function
This is very useful when what you need is to build the bound cursors' path from the component's props.
var mapping = function(props, context) {
return {
name: props.namePath,
color: props.colorCursor
};
};
Know that you can also bind facets to components if needed.
Considering the following tree:
var tree = new Baobab(
{
user: {
name: 'John',
surname: 'Talbot'
},
fruit: 'banana'
},
{
facets: {
fullname: {
cursors: {
user: ['user']
},
get: function(data) {
return `${data.name} ${data.surname}`;
}
}
}
}
);
Binding facets
var mappings = {
facets: {
fullname: 'fullname'
}
};
Binding both cursors and facets
Note that in case of overlapping keys, cursors will win over facets.
// In this case, 'name' will resolve to the cursor's value.
var mappings = {
cursors: {
name: ['user', 'name'],
surname: ['user', 'surname']
},
facets: {
name: 'fullname'
}
};
Controlled input state
If you need to store a react controlled input's state into a baobab tree, remember you have to commit changes synchronously through the tree.commit
method or else you'll observe nasty cursor jumps in some cases.
var Input = React.createClass({
mixins: [mixins.branch],
cursors: {
inputValue: ['inputValue']
},
onChange: function(e) {
var newValue = e.target.value;
// If one edits the tree normally, i.e. asynchronously, the cursor will hop
this.cursor.set(newValue);
// One has to commit synchronously the update for the input to work correctly
this.cursor.set(newValue);
this.tree.commit();
},
render: function() {
return <input onChange={this.onChange} value={this.state.inputValue} />;
}
});
Contributions are obviously welcome.
Be sure to add unit tests if relevant and pass them all before submitting your pull request.
Don't forget, also, to build the files before committing.
# Installing the dev environment
git clone git@github.com:Yomguithereal/baobab-react.git
cd baobab-react
npm install
# Running the tests
npm test
# Linting
npm run lint
# Building a independent version
npm run build
# or per pattern
npm run build-mixins
npm run build-higher-order
npm run build wrappers
npm run build-decorators
MIT