In a React application, it’s very common for a component to contain state that should be editable by a child component. The most basic example uses a text input with a value and onChange handler to display and update the text.

var Editor = React.createClass({ getInitialState: function() { return { text: "" }; }, render: function() { return <input type="text" value={this.state.text} onChange={this.handleChange} />; }, handleChange: function(evt) { this.setState({text: evt.target.value}); } }); View Gist for ex_01.js on GitHub

Doing this over and over, especially in a component with a lot of controlled inputs, can get repetitive. The React addons (available when you use the “React with Add-Ons” download, or via require("react/addons") when using a CommonJS bundler) provide the LinkedStateMixin to simplify situations where an input should remain in lockstep with a given piece of state.

var Editor = React.createClass({ mixins: [React.addons.LinkedStateMixin], getInitialState: function() { return { text: "" }; }, render: function() { return <input type="text" valueLink={this.linkState("text")} />; } }); View Gist for ex_02.js on GitHub

Now the input and the text state will remain synchronized; changing the state via setState will update the input, and changing the input will automatically call setState , updating the text key.

But what does this.linkState(key) , a function provided to our component by LinkedStateMixin , actually return? It’s actually a very simple object created by React’s ReactLink module:

{ value: ..., // current value of this.state[key] requestChange: function() { ... } // function to call to update this.state[key] } View Gist for ex_03.js on GitHub

The value property contains the current value of this.state[key] , and the requestChange property is a function we can call with a new value for this.state[key] to update it.

That means our above example could more verbosely be written like this:

var Editor = React.createClass({ mixins: [React.addons.LinkedStateMixin], getInitialState: function() { return { text: "" }; }, render: function() { var valueLink = this.linkState("text"); var handleChange = function(evt) { valueLink.requestChange(evt.target.value); }; return <input type="text" value={valueLink.value} onChange={handleChange} />; } }); View Gist for ex_04.js on GitHub

Written this way, it’s easy to see that the valueLink property is very straightforward: it simply uses the object’s value property as the current value of the form and the object’s requestChange property as the callback to change that value. We now have all the information we need to implement a valueLink -style property on any component we write.

Extrapolating to Custom Components

Let’s build a simple React component that wraps a simple jQuery color picker. The component wraps a single div, and we utilize React component lifecycle hooks to call appropriate plugin methods when the incoming properties change.

var ColorPicker = React.createClass({ propTypes: { value: React.PropTypes.string, onChange: React.PropTypes.func }, getDefaultProps: function() { return { value: "", onChange: function() {} }; }, render: function() { return <div />; }, componentDidMount: function() { jQuery(this.getDOMNode()).colorPicker({ // initial color from the `value` prop pickerDefault: this.props.value, onColorChange: this.onColorChange }); }, componentWillReceiveProps: function(nextProps) { if (this.props.value !== nextProps.value) { // new `value` prop triggers manual change in plugin var node = jQuery(this.getDOMNode()); node.val(nextProps.value); node.change(); } }, onColorChange: function(id, color) { // color changes sent to parent via `onChange` prop this.props.onChange(color); } }); View Gist for ex_05.js on GitHub

We can use the color picker by passing it value and onChange properties, like so:

It would be nice if our component also supported use of the LinkedStateMixin . Let’s change our application to use valueLink :

Since we know that the valueLink property contains an object with value and requestChange properties, we can easily modify the ColorPicker component:

var ColorPicker = React.createClass({ propTypes: { valueLink: React.PropTypes.shape({ value: React.PropTypes.string.isRequired, requestChange: React.PropTypes.func.isRequired }) }, getDefaultProps: function() { return { valueLink: { value: "", requestChange: function() {} } }; }, render: function() { return <div />; }, componentDidMount: function() { jQuery(this.getDOMNode()).colorPicker({ // initial color from the `valueLink` prop pickerDefault: this.props.valueLink.value, onColorChange: this.onColorChange }); }, componentWillReceiveProps: function(nextProps) { if (this.props.valueLink.value !== nextProps.valueLink.value) { // new `valueLink.value` prop triggers manual change in plugin var node = jQuery(this.getDOMNode()); node.val(nextProps.valueLink.value); node.change(); } }, onColorChange: function(id, color) { // color changes sent to parent via `valueLink` prop this.props.valueLink.requestChange(color); } }); View Gist for ex_08.js on GitHub

However, we’ve lost the ability to use value and onChange with our component, since we’ve hard-coded it to use this.props.valueLink everywhere. We could litter the component with if statements checking for the existence of this.props.valueLink , but instead let’s write a simple abstraction so we don’t have to worry about it:

var ColorPicker = React.createClass({ // ... getValueLink: function(props) { return props.valueLink || { value: props.value, requestChange: props.onChange }; } }); View Gist for ex_09.js on GitHub

This getValueLink function takes a properties object and either returns its valueLink property, if it has one, or creates one from the value and onChange properties. Now, we can write our component as if we always have a valueLink property:

var ColorPicker = React.createClass({ propTypes: { value: React.PropTypes.string, onChange: React.PropTypes.func, valueLink: React.PropTypes.shape({ value: React.PropTypes.string.isRequired, requestChange: React.PropTypes.func.isRequired }) }, getDefaultProps: function() { return { value: "", onChange: function() {}, valueLink: null }; }, getValueLink: function(props) { return props.valueLink || { value: props.value, requestChange: props.onChange }; }, render: function() { return <div />; }, componentDidMount: function() { jQuery(this.getDOMNode()).colorPicker({ pickerDefault: this.getValueLink(this.props).value, onColorChange: this.onColorChange }); }, componentWillReceiveProps: function(nextProps) { var currentValueLink = this.getValueLink(this.props), nextValueLink = this.getValueLink(nextProps); if (currentValueLink.value !== nextValueLink.value) { var node = jQuery(this.getDOMNode()); node.val(nextValueLink.value); node.change(); } }, onColorChange: function(id, color) { this.getValueLink(this.props).requestChange(color); } }); View Gist for ex_10.js on GitHub