How to select all checkboxes in React? - javascript

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?

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

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.

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)
}
}
// ...
}

Related

Why doesn't the table appear?

components/App.js
import React from 'react';
import { connect } from 'react-redux'
import { add_task } from '../actions/index';
class App extends React.Component {
state = {
text: '',
time: ''
}
render_tasks = () => {
const { tasks } = this.props
return (
<table>
<tbody>
{tasks.map(task => {
return (
<tr key={task.id}>
<td>{task.text}</td>
<td>{task.time}</td>
</tr>
)
})
}
</tbody>
</table>
)
}
render() {
console.log('App props', this.props)
return (
<div className="App">
<input type='text'
onChange={(e) => this.setState({
text: e.target.value
})} />
<input type='time'
onChange={(e) => this.setState({
time: e.target.value
})} />
<button
onClick={() => this.props.add_task(
this.state.text, this.state.time)}>
Add
</button>
{this.render_tasks()}
</div>
);
}
}
function mapStateToProps(state) {
return {
tasks: state
}
}
function mapDispatchToProps(dispatch) {
return {
add_task : () => dispatch(add_task())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
actions/index.js
import { ADD_TASK } from './../types';
export const add_task = (text, time) => {
const action = {
type: ADD_TASK,
text,
time
}
console.log('Add action', action)
return action
}
reducers/index.js
import { ADD_TASK } from './../types';
const tasks = (state=[], action) => {
let tasks = [];
if (action.type === ADD_TASK) {
tasks = [...state, {
text: action.text,
time: action.time,
id: Math.random()
}]
console.log('Data from reducer', tasks)
return tasks
} else {
return state
}
}
export default tasks;
When I click the button I expect to get a table with the information I entered in the input fields, but nothing appears, I tried replacing the table part in render_tasks function in App.js with an unordered list and the map returns a list item including 2 spans one for the text and the other for the time but all I got is the dot of the list item!
In
add_task : () => dispatch(add_task())
You don't pass any arguments to add_task().
You can explicitly define the arguments:
add_task : (text, time) => dispatch(add_task(text, time))

Prop keeps returning undefined

I'm currently trying to create a table where each row has a checkbox that can be enabled or disabled.
I have created 3 files:
ChangeUserGroup.jsx - This is where the data is read from a json file
UserGroupForm.jsx - Starts the form and passes the this.prop.permissions on to the next file, which contains the table and the checkboxes.
TableShowPermissions.jsx - Contains the table and the problematic function + checkbox
I would like for the data in the ChangeUserGroup.jsx state(called groupDetails.permissions) to control if the checkbox for the given permission is initialized with "defaultChecked". But i am getting the following error:
The files contain the following:
ChangeUserGroup.jsx
import React, { Component } from 'react';
import { Helmet } from 'react-helmet';
import { Container, Row, Col, Card, CardHeader, CardBody, Table, Button } from 'reactstrap';
import jsonData from '_testdata/userGroups';
import LoadingIcon from '_components/LoadingIcon';
import UserGroupForm from '_components/UserGroupForm';
class ChangeUserGroup extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
userGroupId: 0,
groupDetails: []
}
}
componentDidMount() {
this.setState({ isLoading: true });
const { userGroupId } = this.props.match.params
this.setState({
userGroupId: userGroupId
});
jsonData.userGroups.filter((a) => a.id.toString() === userGroupId).map((b) => this.setState({
groupDetails: b,
isLoading: false
}));
}
render() {
const { groupDetails, isLoading } = this.state;
if (isLoading) {
return <LoadingIcon />;
}
return (
<>
<Helmet>
<title>{global.siteName + " - Brugergrupper"}</title>
</Helmet>
<main>
<section className="section section-shaped section-lg">
<Container>
<Row className="justify-content-center">
<Col lg="12">
<Card className="bg-secondary shadow border-0">
<CardHeader className="bg-white">
<h1 className="text-center">{groupDetails.name}</h1>
</CardHeader>
<CardBody className="px-lg-5 py-lg-5">
<UserGroupForm
id={groupDetails.id}
groupName={groupDetails.name}
position={groupDetails.position}
permissions={groupDetails.permissions} />
</CardBody>
</Card>
</Col>
</Row>
</Container>
</section>
</main>
</>
);
}
}
export { ChangeUserGroup };
UserGroupForm.jsx
import React, { Component } from 'react';
import { Form } from "reactstrap";
import CustomFormInput from '_components/FormStuff/CustomFormInput';
import TableShowPermissions from '_components/UserGroups/TableShowPermissions';
class UserGroupForm extends Component {
render() {
const { id, groupName, position, permissions } = this.props;
return (
<Form>
<CustomFormInput pLabel="Gruppe navn" pIcon="fa-user" pType="text" pShowLabel="on" pValue={groupName} />
<CustomFormInput pLabel="Gruppe position" pIcon="fa-user" pType="text" pShowLabel="on" pValue={position} />
<hr />
<TableShowPermissions
thePermissions={permissions} />
</Form>
);
}
}
export default UserGroupForm;
TableShowPermissions.jsx
import React, { Component } from 'react';
import jsonData from 'UserPermissions';
import { Table, Button } from "reactstrap";
class TableShowPermissions extends Component {
constructor(props) {
super(props);
this.checkIfPermissionAdded = this.checkIfPermissionAdded.bind(this);
}
checkIfPermissionAdded(checkPerm) {
const { thePermissions } = this.props;
thePermissions.split('|').map(permC => {
//console.log(checkPerm + " -- " + permC)
if (checkPerm === permC) {
return true;
}
});
return false;
}
render() {
return (
<Table className="align-items-center table-bordered" responsive>
<thead className="thead-light">
<tr>
<th scope="col">Permission navn</th>
<th scope="col">Beskrivelse</th>
<th scope="col" />
</tr>
</thead>
<tbody>
{jsonData.permissions.map(perm => (
<tr key={perm.id}>
<th scope="row">
{perm.permission}
</th>
<td>
{perm.description}
</td>
<td className="text-right">
<label className="custom-toggle">
<input type="checkbox" defaultChecked={this.checkIfPermissionAdded(perm.permission)} />
<span className="custom-toggle-slider rounded-circle" />
</label>
</td>
</tr>
))}
</tbody>
</Table>
);
}
}
export default TableShowPermissions;
JSON DATA
{
"userGroups": [
{
"id": 1,
"name": "Administrator",
"position": "Admin",
"permissions": "ADMIN_ROOT|"
},
{
"id": 2,
"name": "Moderator",
"position": "Mod",
"permissions": "ADMIN_SETTINGS_GENERAL|ADMIN_CASES_SEEALL|ADMIN_SETTINGS_CATEGORIES"
},
{
"id": 3,
"name": "Supporters",
"position": "Supporter",
"permissions": "ADMIN_CASES_SEEALL|"
}
]
}
Any help would be appreciated! :)
If after a few react renders you do have the value, it most likely means it just isn't there yet in the first renders (this can happen if you are getting the data from a request).
You can either add an if condition to only run the split when thePermissions exist or add a default value to it.
checkIfPermissionAdded(checkPerm) {
const { thePermissions } = this.props;
if (thePermissions) {
thePermissions.split('|').map(permC => {
//console.log(checkPerm + " -- " + permC)
if (checkPerm === permC) {
return true;
}
});
}
return false;
}
or
checkIfPermissionAdded(checkPerm) {
const { thePermissions = '' } = this.props;
thePermissions.split('|').map(permC => {
//console.log(checkPerm + " -- " + permC)
if (checkPerm === permC) {
return true;
}
});
return false;
}
It is possible that none is what you desire in your code but it might help you out knowing why it is happening.
Initialize your Loading initial state as true. Set your state in the constructor like this:
constructor(props) {
super(props);
this.state = {
isLoading: true,
userGroupId: props.match.params,
groupDetails: []
}
}
Remove the first two setState from your componentDidMount. Each time a setState is called the render fucntion is called.

Send argument to setState besides prevState and props?

I have a component that calls a function addOption that is defined on its parent component. The function has a parameter 'option' that is from a form field. The addOption function takes the argument and concats it with an array of options in setState. All this is working fine but, I really want the function called by setState outside of the component so it can be tested and to match other functions in the class, but I can't figure out how to pass the 'option' argument to setState.
class AddOption extends React.Component<IProps> {
onFormSubmit = (e: any) => {
e.preventDefault();
const option = e.target.elements.option.value.trim();
const error = this.props.addOption(option);
}
render() {
return (
<div>
<form onSubmit={this.onFormSubmit}>
<input type="text" name="option" />
<button>Add Option</button>
</form>
</div>
);
}
}
Parent
const deleteOptions = () => ({ options: [] as string[] })
class IndecisionApp extends React.Component<object, State> {
readonly state: State = initialState;
render() {
const { title, subtitle, options } = this.state;
return (
<div>
<Header title={title} subtitle={subtitle} />
<Action
hasOptions={options.length > 0}
onClick={this.handlePickOption}
/>
<Options
onClick={this.handleDeleteOptions}
options={options} />
<AddOption
addOption={this.handleAddOption}
/>
</div>
);
}
handleDeleteOptions = () => this.setState(deleteOptions);
handlePickOption = () => {
const randomNum = Math.floor(this.state.options.length * Math.random());
const option = this.state.options[randomNum];
alert(option);
}
handleAddOption = (option: string) => {
if (!option) {
return "Enter a valid option";
} else if (this.state.options.indexOf(option) > -1) {
return "Enter a unique option";
}
this.setState((prevState: State) => {
return {
options: prevState.options.concat(option)
}
});
}
}
I really want to have handleAddOption more closely resemble deleteOption where its declared outside of the IndecisionApp component, but I don't know how to pass 'option' to setState from handleAddOption.
GitHub - index.tsx
I figured it out.
Inside the component
handleAddOption = (option: string) => {
if (!option) {
return "Enter a valid option";
} else if (this.state.options.indexOf(option) > -1) {
return "Enter a unique option";
}
this.setState(prevState => appOption(prevState, option));
}
Outside the component.
const appOption = (prevState: State, option: string) => {
return {
options: prevState.options.concat(option)
}
}

How can I delete the item if it is stored as an array in reactjs?

Have to display the items via web API. I got the response successfully and displayed the items. But I don't know how to delete the item from the list.
Displayed the delete icon to every item, but I am not able to delete the item from the state. What did I wrong? Please help me out.
import React from 'react';
class App extends React.Component {
constructor() {
super();
this.state = {
posts: []
}
this.UserList = this.UserList.bind(this);
this.setStateHandler = this.setStateHandler.bind(this);
this.setDeleteHandler = this.setDeleteHandler.bind(this);
}
componentDidMount() {
this.UserList();
}
setStateHandler() {
alert(0)
var item = {
"url": "http://www.imdb.com/title/tt4937812/",
"title": "PAMMAL K SAMBANTHAM",
"imdb_id": "tt4937812",
"type": "Film",
"year": "2015"
};
var myArray = this.state.posts;
myArray.push(item)
console.log(myArray)
this.setState({posts: myArray})
};
setDeleteHandler() {
alert("idris")
};
UserList() {
alert(1)
fetch('http://www.theimdbapi.org/api/person?person_id=nm0352032').then(response => {
return response.json();
}).then(data => {
const posts = data.filmography.soundtrack;
this.setState({posts});
});
}
render() {
return (
<div>
<div>
{
this.state.posts.map((item, i) => {
return <Content item={item} key={i}/>
})
}
</div>
<button onClick = {this.setStateHandler}>SET STATE</button>
</div>
);
}
}
class Content extends React.Component {
render() {
return (
<div>
<table>
<tbody>
<tr>
<td>{this.props.item.title}</td>
<td>{this.props.item.type}</td>
<td>{this.props.item.type}</td>
<td><button onClick = {this.setDeleteHandler}>Delete</button></td>
</tr>
</tbody>
</table>
</div>
);
}
}
export default App;
Can anyone please help on this?
Thank you.
Make your setDeleteHandler like this
setDeleteHandler(index) {
return () => {
let posts = [...this.state.posts];
posts.splice(index,1);
this.setState({posts});
}
};
Then pass this function to your content component like this
this.state.posts.map((item, i) => {
return <Content item={item} key={i} deleteHandler={this.setDeleteHandler(i)}/>
})
And then in your content component call this function like this
<td><button onClick = {this.props.deleteHandler}>Delete</button></td>

Cannot read property 'map' of undefined ( ReactJS & AJAX)

Try show in table my array. Array get from AJAX request in Action. I'm using Redux
class IncomeProfile extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
this.props.IncomeListProfile();
}
render() {
var elems = this.props.items.course_list;
console.log(elems);
return (
<div>
<table>
{elems.map((item) => (
<tr key={item.course_id}>
<td>{item.name}</td>
</tr>
))}
</table>
</div>
)
}
}
const mapDispatchToProps = function(dispatch) {
return {
IncomeListProfile: () => dispatch(IncomeProfileList())
}
}
const mapStateToProps = function(state) {
var mystore = state.toArray();
//console.log(mystore[6]);
return {
items: mystore[6]
};
}
export default connect(mapStateToProps, mapDispatchToProps)(IncomeProfile);
Console.log first print "undefined" then it print this:
Try add condition in render method if (elems) { } not helps
Make use of the following
var elems = this.props.items['course_list'];
var copy = Object.assign({}, elems);
console.log(elems.course_list);

Categories

Resources