2
votes

I created a basic interface using checkboxes that used a react design pattern that has served me well before and that I thought worked well - namely lifting up state and passing down props to UI components. My checkbox components are passed a value(a metric), an state-changing method, and a boolean for checked. The problem is that the checkboxes do not update immediately, even though you can see them updating in the React dev tools. They only update on the next click, as in when another checkbox is checked. Here is the code:

class App extends React.Component {
 constructor(props) {
  super(props);
  this.state = {
   metricsSelected: []
  }
  this.selectMetric = this.selectMetric.bind(this)
 }

selectMetric(metric) {
 const metricsSelected = this.state.metricsSelected
 const index = metricsSelected.indexOf(metric)
 if (index !== -1){
  metricsSelected.splice(index, 1)
 }
 else {
  metricsSelected.push(metric)
 }
 this.setState({
  metricsSelected,
 });
}

render() {
 return (
  <div>
    <Sidebar
      metricsSelected={this.state.metricsSelected}
      selectMetric={this.selectMetric}/>
    <SomethingElse/>
   </div>
  )
 }
}

const SomethingElse = () => (<div><h2>Something Else </h2></div>)

const Sidebar = ({ metricsSelected, selectMetric }) => {
 const metrics = ['first thing', 'second thing', 'third thing']
 return (
  <div>
   <h3>Select Metrics</h3>
   { metrics.map( (metric, i) =>
    <Checkbox
      key={i}
      metric={metric}
      selectMetric={selectMetric}
      checked={metricsSelected.includes(metric)}/>
    )}
   </div>
  )
 }

const Checkbox = ({ metric, selectMetric, checked }) => {

const onChange = e => {
 e.preventDefault()
 selectMetric(e.target.value)
}
return (
  <ul>
   <li>{metric}</li>
   <li><input
   type='checkbox'
   value={metric}
   checked={checked}
   onChange={onChange} /></li>
  </ul>
 )
}

I've read pretty much everything I can get my hands on about checkboxes for react and most of the applications of the checkbox are doing something different from what I want to do. I've tried adding state to the Checkbox component, but that didn't seem to help, since the checked value still needs to come in from elsewhere. I thought react components rerendered when the props changed. What gives?

Here's a codepen: https://codepen.io/matsad/pen/QpexdM

1
did some changes, check this it will be very fast: codepen.io/anon/pen/xdKegq?editors=0010. main issue was e.preventDefault() remove that.Mayank Shukla

1 Answers

0
votes

Here is a working version: http://codepen.io/TLadd/pen/oWvOad?editors=1111

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      metricsSelected: {}
    }
    this.selectMetric = this.selectMetric.bind(this)
  }

  selectMetric(metric) {
    this.setState(({ metricsSelected }) => ({
      metricsSelected: {
        ...metricsSelected,
        [metric]: !metricsSelected[metric]
      }
    }))
  }

  render() {
    return (
      <div>
        <Sidebar
          metricsSelected={this.state.metricsSelected}
          selectMetric={this.selectMetric}/>
        <SomethingElse/>
      </div>
    )
  }
}

const SomethingElse = () => (<div><h2>Something Else </h2></div>)

const Sidebar = ({ metricsSelected, selectMetric }) => {
  const metrics = ['first thing', 'second thing', 'third thing']
  return (
    <div>
      <h3>Select Metrics</h3>
      { metrics.map( (metric, i) =>
        <Checkbox
          key={i}
          metric={metric}
          selectMetric={selectMetric}
          checked={Boolean(metricsSelected[metric])}/>
        )}
    </div>
  )
}

const Checkbox = ({ metric, selectMetric, checked }) => {
  return (
  <ul>
    <li>{metric}</li>
    <li>
      <input
        type='checkbox'
        name={metric}
        checked={checked}
        onChange={() => selectMetric(metric)} 
       />
     </li>
   </ul>
  )
}


ReactDOM.render(
  <App />,
  document.getElementById('root')
);

The couple of things that were causing issues were that you were mutating state in selectMetric, and your checkbox input's onChange function is using e.target.value instead of e.target.checked.

I changed the metricsSelected state to be an object, since I think it makes the management of it quite a bit easier.