0

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 every time I want to use this feature. I'm looking for a way within react to make this modular? Is this

It doesn't modularize the entire functionality of the "input checked all" functionality in one place. I have to move the getInitialState variables and changeHandlers with each usage.

I think of it like if the "input checkbox all" functionally was native within HTML, how would we use it? We would need only supply the attributes to the elements and they would reference each other and all of the handlers would occur under the hood, it would be simple to use. My goal with this example is to have HTML level simplicity. The code I show above doesn't achieve that because it's tied down to function handlers and state initializers. Does react provide a way of abstracting this?

Below is my desired API for this component.

Here's the working example.

The main problems are:

  • The component functionality is indifferent to the parent, meaning the paren't doesn't need to store the information for the handlers and state.
  • The code currently manually tracks state for each checkbox, meaning there's no way to dynamically find how many of an a checkbox is in the DOM without stating it.
  • Overall modularity and ease-of use.

Here's the code:

var InputCheckbox = React.createClass({
  getDefaultProps: function () {
    return {
      checked: false
    }
  },
  render: function () {
    return (
    <input
           checked={this.props.checked}
           type='checkbox'
           {...this.props}/>
    )
  }
})

var Test = React.createClass({
  getInitialState: function () {
    return {
      checked: [false, false, false]
    }
  },
  selectAll: function (event) {
    // Set all checked states to true
    this.setState({
      checked: this.state.checked.map(function () {
        return event.target.checked
      })
    })
  },
  handleChange: function (index, event) {
    var checked = this.state.checked
    checked[index] = event.target.checked
    this.setState({
      checked: checked
    })
  },
  render: function () {
    var isAllChecked = this.state.checked.filter(function (c) {
        return c
      }).length === this.state.checked.length

    return (
    <div>
      Select All:
      <InputCheckbox onChange={this.selectAll} checked={isAllChecked}/>
      <br/>
      <InputCheckbox checked={this.state.checked[0]} onChange={this.handleChange.bind(this, 0)}/>
      <br/>
      <InputCheckbox checked={this.state.checked[1]} onChange={this.handleChange.bind(this, 1)}/>
      <br/>
      <InputCheckbox checked={this.state.checked[2]} onChange={this.handleChange.bind(this, 2)}/>
      <br/>
    </div>
    )
  }
})

React.render(<Test/>, document.body)

Ideally I can just use it like this:

var Checkbox = require('./Checkbox')

var Test = React.createClass({
  render: function () {
    return (
      <div>
        <Checkbox id="alpha"/>
        <Checkbox htmlFor="alpha"/>
        <Checkbox htmlFor="alpha"/>
        <Checkbox htmlFor="alpha"/>
      </div>
    )
  }
})
3
  • Now you have Test component to manage the state of the checkboxes. Better to move that logic into Store. And wrap each checkbox into a wrapper component that reads the checked state from the store and passes it into the checkbox as a prop. Commented Sep 18, 2015 at 11:52
  • @oluckyman Hey, can you demonstrate what this looks like in an answer? A little confused what you mean by "wrap each checkbox". Commented Sep 18, 2015 at 19:43
  • Extracting data into the store would be the opposite of what was asked - any component linked to the store is not re-usable in different areas or apps Commented Sep 18, 2015 at 19:51

2 Answers 2

1

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);
    })
  }
})
Sign up to request clarification or add additional context in comments.

2 Comments

Nice job. There's one major problem, when I put a <Checkbox> within a table or paragraph or anywhere the map can't find it. The <Checkbox> has to be a top level element right under <CheckboxGroup>. For most use cases this doesn't work.
I've updated the example to show how you can have children be other than top-level elements.
0

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.