James Thibaudeau James Thibaudeau - 3 months ago 24
React JSX Question

How to manage state with checkboxes in React?

I am new to React and I am trying to make a table of users and want to use checkboxes to manage their permissions. I have run into a problem with updating the state when I click a checkbox. The handleChange method is where I am having problems. I don't know how to go about identifying the right checkbox to change the state for that specific user. I am thinking maybe I need to add an id prop to each but that seems like it might get out of hand for a large number of users, i.e. one id for each permission per user. I feel like this shouldn't be so difficult but I have been stuck for a long time.

My component code is below.

import React from 'react'
import {Link} from 'react-router'
import {Panel, Button, PageHeader, Row, Col, Table, Input} from 'react-bootstrap'


export class UserPermissions extends React.Component {

constructor() {
super();
this.state = {
users: [
{
name: 'Jerry',
viewAccounts: true,
modifyAccounts: true,
viewUsers: false,
modifyUsers: true
},
{
name: 'George',
viewAccounts: false,
modifyAccounts: true,
viewUsers: false,
modifyUsers: false
},
{
name: 'Elaine',
viewAccounts: true,
modifyAccounts: false,
viewUsers: false,
modifyUsers: true
}
]
}
}

handleChange(e){
//not sure how to write this
}

renderHeadings(data){
return data.map((step, index) => <th key={index} style={{align:"center"}}>{step}</th>);

}

renderRows(data){
return data.map((step, index) =>
<tr key={index}>
<td>{step['name']}</td>
<td style={{align:"center", paddingLeft:"40px"}}>
<Input type="checkbox"
checked={step['viewAccounts']}
onChange={this.handleChange}/></td>
<td style={{align:"center", paddingLeft:"40px"}}>
<Input type="checkbox"
checked={step['modifyAccounts']}
onChange={this.handleChange}/></td>
<td style={{align:"center", paddingLeft:"40px"}}>
<Input type="checkbox"
checked={step['viewUsers']}
onChange={this.handleChange}/></td>
<td style={{align:"center", paddingLeft:"40px"}}>
<Input type="checkbox"
checked={step['modifyUsers']}
onChange={this.handleChange} /></td>
<td style={{align:"center"}}>
<Link to="/users"><i className="fa fa-edit fa-2x fa-fw" /></Link>
<Link to="/users"><i className="fa fa-times-circle fa-2x fa-fw" /></Link></td>
</tr>
);

}

render() {
return (
<div>
<Row>
<Col lg={12}>
<PageHeader>User Permissions</PageHeader>
</Col>

<Col lg={12}>
<Panel header={<span>Users</span>} bsStyle="primary">
<div>
<div className="dataTable_wrapper">
<div id="dataTables-example_wrapper" className="dataTables_wrapper form-inline dt-bootstrap no-footer">
<Row>
<Col sm={12}>
<Table striped condensed responsive>
<thead>
<tr>
{this.renderHeadings(this.props.headings)}
</tr>
</thead>
<tbody>
{this.renderRows(this.state.users)}
</tbody>
</Table>
</Col>
</Row>
</div>
</div>
</div>
</Panel>
<Button bsStyle="success">Add User</Button>
</Col>
</Row>
</div>
);
}
}

UserPermissions.propTypes = {
headings: React.PropTypes.array
}

UserPermissions.defaultProps = {
headings: ['Name', 'View Accounts', 'Modify Accounts', 'View Users', 'Modify Users']

}

Answer

First, you should add id's to each user. Identifying users by their name is a bad practice:

constructor() {
  super();
  this.state = {
    users: [
      {   
        id: 1,
        name: 'Jerry',
        viewAccounts: true,
        modifyAccounts: true,
        viewUsers: false,
        modifyUsers: true
      },
      { 
        id: 2,  
        name: 'George',
        viewAccounts: false,
        modifyAccounts: true,
        viewUsers: false,
        modifyUsers: false
      },
      {   
        id: 2,
        name: 'Elaine',
        viewAccounts: true,
        modifyAccounts: false,
        viewUsers: false,
        modifyUsers: true
      }
    ]
  }               
}

Next, you should provide to this.handleChange function id of user, name of property we are changing, and current value:

renderRows(data) {
  return data.map((step, index) => 
    <tr key={index}>
      <td>{step['name']}</td>
      <td style={{align:"center", paddingLeft:"40px"}}>
        <Input type="checkbox"
          checked={step['viewAccounts']} 
          onChange={e => this.handleChange(step.id, 'viewAccounts', step['viewAccounts'])}/></td>
      <td style={{align:"center", paddingLeft:"40px"}}>
        <Input type="checkbox"
          checked={step['modifyAccounts']} 
          onChange={e => this.handleChange(step.id, 'modifyAccounts', step['modifyAccounts'])}/></td>
      <td style={{align:"center", paddingLeft:"40px"}}>
        <Input type="checkbox"
          checked={step['viewUsers']} 
          onChange={e => this.handleChange(step.id, 'viewUsers', step['viewUsers'])}/></td>
      <td style={{align:"center", paddingLeft:"40px"}}>
        <Input type="checkbox"
          checked={step['modifyUsers']}
          onChange={e => this.handleChange(step.id, 'modifyUsers', step['modifyUsers'])}/></td>
      <td style={{align:"center"}}>
        <Link to="/users"><i className="fa fa-edit fa-2x fa-fw" /></Link>
        <Link to="/users"><i className="fa fa-times-circle fa-2x fa-fw" /></Link></td>
    </tr>
  );
}

And lastly, in this.handleChange function, we should update particular user data according given values:

handleChange(id, name, value) {
  this.setState({
    users: this.state.users.map((user) => {
      if (user.id !== id) return user;

      // `Object.assign` function is used to return new modified object.
      return Object.assign({}, user, {
        // We should assign opposite to `value` variable value, as we are toggling permissions.
        [name]: !value
      });
    });
  });
}
Comments