How to select all checkboxes in React?

16,851

Solution 1

While some of the answers provided the specific functionality of selecting all the checkboxes, I needed also common functionalities like deselecting all, selecting all then deselecting some, when manually selecting all the select all box checks on as well etc... So I wrote all of that and post it as an answer here. Thanks to everyone who replied. This code is based on Mayank Shuklas answer. Note that it may not still be perfect as I didn't properly tested it yet and definitely it needs some refactoring.

import React, { Component } from 'react'
import EmailListItem from './EmailListItem'
import { createContainer } from 'meteor/react-meteor-data'
import { Emails } from '../../../../../imports/collections/emails/Emails'

class EmailList extends Component {
  constructor (props) {
    super(props)
    this.state = {
      selectedEmails: new Set(),
      checked: false
    }
  }

  handleSelectedEmails (allSelected, individualSelected, selectedEmail, checked) {
    console.log('allSelected', allSelected)
    console.log('individualSelected', individualSelected)
    console.log('selectedEmail', selectedEmail)
    console.log('checked', checked)
    let selectedEmails = this.state.selectedEmails
    if (allSelected && !individualSelected) {
        this.props.emails.forEach((email) => {
          selectedEmails.add(email._id)
        })
    } else if (!allSelected && !individualSelected) {
      selectedEmails.clear()
    } else if (individualSelected) {
      if (checked) {
        selectedEmails.add(selectedEmail)
        if (selectedEmails.size === this.props.emails.length) {
          this.checkAll()
        }
      } else {
        selectedEmails.delete(selectedEmail)
        this.setState({checked})
      }
    }
    this.setState({selectedEmails})
    console.log('selectedEmails', this.state.selectedEmails)
  }
  removeSelected () {
    const selectedEmails = Array.from(this.state.selectedEmails)
    Meteor.call('emails.remove', selectedEmails, (err, result) => {
      if (err) console.log(err)
      if (result) console.log(result)
    })
  }
  checkAll () {
    this.setState({checked: !this.state.checked})
    console.log('chcekedClick', this.state.checked)
    this.handleSelectedEmails(!this.state.checked, false)
  }
  renderList () {
    console.log(this.props)
    return this.props.emails.map(email => {
      // console.log(email)
      const { name, opr, ctr, _id } = email
      const createdAt = email.createdAt.toDateString()
      const link = `/dashboard/emailpreview/${_id}`
      return (
        <EmailListItem
          handleSelectedEmails={this.handleSelectedEmails.bind(this)}
          name={name}
          createdAt={createdAt}
          opr={opr}
          ctr={ctr}
          link={link}
          key={email._id}
          id={email._id}
          value={this.state.checked || this.state.selectedEmails.has(email._id)} />
        )
    })
  }
  render () {
    // TODO: make checks with state
    return (
      <div className="email_list">
        <table>
          <thead>
            <tr>
              <td><input onChange={this.checkAll.bind(this)} type="checkbox" checked={this.state.checked} /></td>
              <td>Title<button onClick={this.removeSelected.bind(this)} className="btn btn-danger">Remove</button></td>
              <td>Dates</td>
              <td>Open Rates</td>
              <td>CTA</td>
            </tr>
          </thead>
          <tbody>
            {this.renderList()}
          </tbody>
        </table>

      </div>
  )
  }
}

export default createContainer(() => {
  Meteor.subscribe('emails')
  return { emails: Emails.find({}).fetch() }
}, EmailList)

And the EmailListItem

import React, { Component } from 'react'
import { Link } from 'react-router'

class EmailListItem extends Component {
  render () {
    console.log('_id', this.props.id)
    return (
      <tr>
        <td><input ref="myCheckbox"
          onChange={(event) => {
            this.props.handleSelectedEmails(false, true, event.target.name, event.target.checked)
          }}
          checked={this.props.value}
          type="checkbox" name={this.props.id} /></td>
        <td><Link to={this.props.link}>{this.props.name}</Link></td>
        <td>{this.props.createdAt}</td>
        <td>Open Rates</td>
        <td>CTA</td>
      </tr>
    )
  }
}

export default EmailListItem

Solution 2

I think, maintaining individual states for each email id is not required, you are already storing the values in parent component, pass the value from parent in props, other thing is for selecting all email ids, you are maintaining a bool in parent, at the time of passing the value in props check that bool, if the bool is true then pass true otherwise check in the set and pass the result returned by set.

Check the working solution of jsfiddle: https://jsfiddle.net/h17mcjwa/

try this renderList method:

renderList () {
 return this.props.emails.map(email => {
      const { name, opr, ctr, _id } = email
      const createdAt = email.createdAt.toDateString()
      const link = `/dashboard/emailpreview/${_id}`
      return (
        <EmailListItem
          handleSelectedEmails={this.handleSelectedEmails.bind(this)}
          name={name}
          createdAt={createdAt}
          opr={opr}
          ctr={ctr}
          link={link}
          key={email._id}
          id={email._id} 
          value={this.state.checked || this.state.selectedEmails.has(email._id)}
        />
      )
   })
 }

And use this component:

class EmailListItem extends Component {
  constructor (props) {
    super(props)
    //this.state = {
    //  checked: false
    //}
  }

  //checkedClick () {
  //  this.setState({checked: !this.state.checked})
  //  console.log('chcekedClick')
  //}

  //componentDidUpdate () {
  //  if (this.props.selecetedAllEmails) {
  //    this.checkedClick()
  //    this.props.handleSelectedEmails(myCheckbox.name, myCheckbox.checked)
  //  }
  //}

  render () {
    return (
      <tr>
        <td><input ref="myCheckbox"
          onChange={(event) => {
            this.props.handleSelectedEmails(event.target.name, event.target.checked)
          }}
          checked={this.props.value}
          type="checkbox" name={this.props.id} /></td>
        <td><Link to={this.props.link}>{this.props.name}</Link></td>
        <td>{this.props.createdAt}</td>
        <td>Open Rates</td>
        <td>CTA</td>
      </tr>
    )
  }
}

Let me know if it doesn't work for u.

Solution 3

You could use componentWillReceiveProps instead of componentDidUpdate:

class EmailListsItem extends Component {
  // ...
  componentWillReceiveProps (nextProps) {
    const { myCheckbox } = this.refs

    // if selecetedAllEmails is updated from false to true
    if (nextProps.selecetedAllEmails && !this.props.selecetedAllEmails) {
      this.checkedClick()
      this.props.handleSelectedEmails(myCheckbox.name, myCheckbox.checked)
    }
  }
  // ...
}
Share:
16,851
Hayk Safaryan
Author by

Hayk Safaryan

Updated on June 16, 2022

Comments

  • Hayk Safaryan
    Hayk Safaryan almost 2 years

    I have this module:

    import React, { Component } from 'react'
    import EmailListItem from './EmailListItem'
    import { createContainer } from 'meteor/react-meteor-data'
    import { Emails } from '../../../../../imports/collections/emails/Emails'
    
    class EmailList extends Component {
      constructor (props) {
        super(props)
        this.state = {
          selectedEmails: new Set(),
          checked: false
        }
      }
    
      handleSelectedEmails (selectedEmail, checked) {
        let selectedEmails = this.state.selectedEmails
        if (checked) {
          selectedEmails.add(selectedEmail)
        } else {
          selectedEmails.delete(selectedEmail)
        }
        this.setState({selectedEmails})
        console.log('selectedEmails', this.state.selectedEmails)
      }
      removeSelected () {
        const selectedEmails = Array.from(this.state.selectedEmails)
        Meteor.call('emails.remove', selectedEmails, (err, result) => {
          if (err) console.log(err)
          if (result) console.log(result)
        })
      }
      checkedClick () {
        this.setState({checked: !this.state.checked})
        console.log('chcekedClick')
      }
      renderList () {
        console.log(this.props)
        return this.props.emails.map(email => {
          console.log(email)
          const { name, opr, ctr, _id } = email
          const createdAt = email.createdAt.toDateString()
          const link = `/dashboard/emailpreview/${_id}`
          return (
            <EmailListItem
              selecetedAllEmails={this.state.checked}
              handleSelectedEmails={this.handleSelectedEmails.bind(this)}
              name={name}
              createdAt={createdAt}
              opr={opr}
              ctr={ctr}
              link={link}
              key={email._id}
              id={email._id} />
            )
        })
      }
      render () {
        // TODO: make checks with state
        return (
          <div className="email_list">
            <table>
              <thead>
                <tr>
                  <td><input onChange={this.checkedClick.bind(this)} type="checkbox" checked={this.state.checked} /></td>
                  <td>Title<button onClick={this.removeSelected.bind(this)} className="btn btn-danger">Remove</button></td>
                  <td>Dates</td>
                  <td>Open Rates</td>
                  <td>CTA</td>
                </tr>
              </thead>
              <tbody>
                {this.renderList()}
              </tbody>
            </table>
    
          </div>
      )
      }
    }
    
    export default createContainer(() => {
      Meteor.subscribe('emails')
      return { emails: Emails.find({}).fetch() }
    }, EmailList)
    

    And it renders this module

    import React, { Component } from 'react'
    import { Link } from 'react-router'
    
    class EmailListItem extends Component {
      constructor (props) {
        super(props)
        this.state = {
          checked: false
        }
      }
    
      checkedClick () {
        this.setState({checked: !this.state.checked})
        console.log('chcekedClick')
      }
    
      componentDidUpdate () {
        console.log('componentDidUpdate')
        const { myCheckbox } = this.refs
        console.log('myCheckbox', myCheckbox)
        console.log('myCheckbox.name', myCheckbox.name)
        console.log('myCheckbox.checked', myCheckbox.checked)
        if (this.props.selecetedAllEmails) {
          console.log('componentDidUpdate IF')
          this.checkedClick()
          this.props.handleSelectedEmails(myCheckbox.name, myCheckbox.checked)
        }
      }
      render () {
        console.log('_id', this.props.id)
        return (
          <tr>
            <td><input ref="myCheckbox"
              onChange={(event) => {
                this.checkedClick()
                this.props.handleSelectedEmails(event.target.name, event.target.checked)
              }}
              checked={this.state.checked}
              type="checkbox" name={this.props.id} /></td>
            <td><Link to={this.props.link}>{this.props.name}</Link></td>
            <td>{this.props.createdAt}</td>
            <td>Open Rates</td>
            <td>CTA</td>
          </tr>
        )
      }
    }
    
    export default EmailListItem
    

    As you can see, for each email item I have a checkbox. I can select a few checkboxes, and click that remove button which will call remove my selected items. Now in the top I have a checkbox which should select all the checkboxes. My solution to this was to store the global checkbox checked and pass it as a prop to all the items. Then in the items I perform a check on componentDidUpdate and if the global checkbox is selected then I check that item as well. But this results in an infinite loop. What would be the best solution here?