Search code examples
javascriptuser-interfacereactjscheckboxweb-frontend

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']

}

Solution

  • 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
          });
        });
      });
    }