Reputation: 2046
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?
Upvotes: 4
Views: 19077
Reputation: 2046
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
Upvotes: 5
Reputation: 104369
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.
Upvotes: 3
Reputation: 7738
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)
}
}
// ...
}
Upvotes: 1