I have below a working component that allows for a checkbox all and checkboxes. It works perfectly. However I hate the idea that I\'m stuck carrying all of this code around
The following example is in the right general direction I think, the general idea is to introduce a wrapper component for the related boxes, and then walk through the children in that component to tie them together.
var CheckAll = React.createClass({
render() {
return <input type="checkbox" {...this.props} />
}
});
var Checkbox = React.createClass({
render() {
return <input type="checkbox" {...this.props} />
}
});
var CheckboxGroup = React.createClass({
setAll(to) {
var result = {};
Object.keys(this.props.boxes).forEach(k => result[k] = to)
this.props.onChange(result);
},
setOne(name, to) {
var result = {};
Object.keys(this.props.boxes).forEach(k => result[k] = this.props.boxes[k])
result[name] = to;
this.props.onChange(result);
},
enrichChild(child) {
var boxes = this.props.boxes;
var all = Object.keys(boxes).every(k => boxes[k]);
if (child.type == CheckAll) {
return React.cloneElement(child, { checked: all,
onChange: () => this.setAll(!all)
});
} else if (child.type == Checkbox) {
var name = child.props.name;
return React.cloneElement(child, { checked: !!boxes[name],
onChange: ({target}) => this.setOne(name, target.checked)
});
} else {
return child;
}
},
render() {
return (
<div>
{React.Children.map(this.props.children, this.enrichChild)}
</div>
)
}
});
var Test = React.createClass({
getInitialState: function () {
return {
boxes: {
a: true,
b: false,
c: false,
}
}
},
render: function () {
return (
<div>
<CheckboxGroup
boxes={this.state.boxes}
onChange={boxes => this.setState({boxes})}
>
<CheckAll />
<Checkbox name="a" />
<Checkbox name="b" />
<Checkbox name="c" />
</CheckboxGroup>
</div>
)
}
})
React.render(<Test/>, document.body)
Here's a jsbin - https://jsbin.com/zomuxolevo/1/edit?js,output
To allow for more flexibility with the children, you'd need to recursively walk them using something like this gist https://gist.github.com/dandelany/1ff06f4fa1f8d6f89c5e
var RecursiveChildComponent = React.createClass({
render() {
return <div>
{this.recursiveCloneChildren(this.props.children)}
</div>
},
recursiveCloneChildren(children) {
return React.Children.map(children, child => {
if(!_.isObject(child)) return child;
var childProps = {someNew: "propToAdd"};
childProps.children = this.recursiveCloneChildren(child.props.children);
return React.cloneElement(child, childProps);
})
}
})
I hacked this together using some jQuery, and lodash.
Here's the example running.
Note, this example goes into the DOM to get the data needed. None of the state of these checkboxes are stored by the component. As far as I can tell there is no true "React" way to do this. (I am very open to suggestion.)
var Checkbox = React.createClass({
componentDidMount: function () {
var component = React.findDOMNode(this)
var $component = $(component)
if ($component.attr('id')) {
var selector = 'input'
selector += '[data-component=Checkbox]'
selector += '[for=' + $component.attr('id') + ']'
var $forComponents = $(selector)
$component.on('change', function () {
var value = $component.prop('checked')
$forComponents.each(function () {
$forComponent = $(this)
$forComponent.prop('checked', value)
})
})
$forComponents.on('change', function () {
var values = $forComponents.map(function () {
var $forComponent = $(this)
var value = $forComponent.prop('checked')
return value
})
var simple = _.chain(values).unique().value()
if (simple.length === 1 && simple[0] === true) {
$component.prop('checked', true)
} else {
$component.prop('checked', false)
}
})
}
},
render: function () {
return (
<input
type='checkbox'
data-component='Checkbox'
{...this.props}/>
)
}
})
var Test = React.createClass({
render: function () {
return (
<div>
Select All: <Checkbox id='alpha'/><br/>
<Checkbox htmlFor='alpha'/><br/>
<Checkbox htmlFor='alpha'/><br/>
<Checkbox htmlFor='alpha' defaultChecked/><br/>
<Checkbox htmlFor='alpha'/><br/>
<Checkbox htmlFor='alpha'/><br/>
</div>
)
}
})
React.render(<Test/>, document.body)