Select/Unselect All checkbox in reactJs - javascript

I am trying to implement select/unselect all functionality in reactJs but couldn't make it happen.
I have made select/unselect all main header checkbox functional and the single elements can also be selected or unselected.
My work link:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class Box extends Component{
constructor(props){
super(props);
this.state = {
allChecked: false,
list: [
{id:1, name: "item1", isChecked: false},
{id:2, name: "item2", isChecked: false},
{id:3, name: "item3", isChecked: false},
],
};
}
handleChange = (e) => {
let list = this.state.list;
let allChecked = this.state.allChecked;
if(e.target.value === "checkAll"){
list.forEach(item => {
item.isChecked = e.target.checked;
allChecked = e.target.checked;
});
}
else{
list.find( item => item.name === e.target.name).isChecked = e.target.checked;
}
this.setState({list:list, allChecked: allChecked});
}
renderList = () => {
return this.state.list.map(item =>
<div>
<input key={item.id} type="checkbox" name={item.name} value={item.name} checked={item.isChecked} onChange={this.handleChange} />
<label>{item.name}</label>
</div>
)
}
render(){
return(
<div>
<input type="checkbox"
value="checkAll"
checked={this.state.allChecked}
onChange={this.handleChange} />Check all
<br/>
{this.renderList()}
</div>
);
}
}
ReactDOM.render(<Box/>, document.getElementById('root'));
To be straight forward, i want this (https://jsfiddle.net/52uny55w/) type of functionality using the plain Javascript but not with the jquery for some reasons.

I have solved the problem at https://codesandbox.io/s/vvxpny4xq3
handleChange = e => {
let itemName = e.target.name;
let checked = e.target.checked;
this.setState(prevState => {
let { list, allChecked } = prevState;
if (itemName === "checkAll") {
allChecked = checked;
list = list.map(item => ({ ...item, isChecked: checked }));
} else {
list = list.map(item =>
item.name === itemName ? { ...item, isChecked: checked } : item
);
allChecked = list.every(item => item.isChecked);
}
return { list, allChecked };
});
};
A few things to note.
1) I have updated the checkAll button to have a name property to ensure consistency
2) If modifying the existing state, use the new functional syntax
3) Destructure the objects and do not mutate them in place if possible. You could use map instead of forEach and use spread operator to modify the object without mutating.

Related

Collect checkbox values as an array React

I have a checkbox component, I want my user to be able to check multiple items, and then the items to be saved in the state as an array.
If I select a checkbox my handleChange function seems to set my array to undefined, I'm not sure if it's the way I am sending the data or If I've setup my checkbox wrong, I'm quite new to React.
My main component is
export default class MainForm extends Component {
state = {
eventFormats: []
}
handleChange = input => event => {
this.setState({[input]: event.target.value})
console.log(this.state)
}
render() {
const eventFormat = {eventFormats: this.state.eventFormats}
return <EventFormat
nextStep={this.nextStep}
handleChange={this.handleChange}
values={eventFormat}
}
}
}
My event form component
export default class EventFormat extends Component {
state = {
eventFormats: [
{id: 1, value: 1, label: "Virtual", isChecked: false},
{id: 2, value: 2, label: "Hybrid", isChecked: false},
{id: 3, value: 3, label: "Live", isChecked: false},
]
}
saveAndContinue = (e) => {
e.preventDefault()
}
render() {
return (
<Form>
<h1 className="ui centered">Form</h1>
<Form.Field>
{
this.state.eventFormats.map((format) => {
return (<CheckBox handleChange={this.props.handleChange} {...format} />)
})
}
</Form.Field>
<Button onClick={this.saveAndContinue}>Next</Button>
</Form>
)
}
}
And finally my checkbox component
const CheckBox = (props) => {
return (<Checkbox label={props.label} onChange={props.handleChange('eventFormats')}/>)
}
export default CheckBox
The error is in your handleChange function, which sets state to a dictionary while you said you want the checkbox's value to be added to the eventFormats array in the state.
export default class MainForm extends Component {
state = {
eventFormats: []
}
handleChange = input => event => {
if (event.target.checked) {
this.setState({eventFormats: this.state.eventFormats.concat([event.target.value])});
} else {
const index = this.state.indexOf(event.target.value);
if (index === -1) {
console.error("checkbox was unchecked but had not been registered as checked before");
} else {
this.setState({eventFormats: this.state.eventFormats.splice(index, 1);
}
}
console.log(this.state)
}
render() {
const eventFormat = {eventFormats: this.state.eventFormats}
return <EventFormat
nextStep={this.nextStep}
handleChange={this.handleChange}
values={eventFormat}
}
}
}
There are a few things to fix:
this.setState({[input]: event.target.value})
this will always overwrite the array(eventFormats) with event.target.value.
<CheckBox handleChange={this.props.handleChange} {...format} />
in the above line, you're passing all the properties in each format object
const CheckBox = (props) => {
return (<Checkbox label={props.label} onChange={props.handleChange('eventFormats')}/>)
}
but here you're only using label and handleChange.
Here's a React StackBlitz that implements what you're looking for. I used <input type="checkbox" />, you can replace this with the Checkbox component you want. See the console logs to know how the state looks after toggling any of the checkboxes.
Also, added some comments to help you understand the changes.
const Checkbox = ({ id, checked, label, handleChange }) => {
return (
<>
<input
type="checkbox"
id={id}
value={checked}
// passing the id from here to figure out the checkbox to update
onChange={e => handleChange(e, id)}
/>
<label htmlFor={id}>{label}</label>
</>
);
};
export default class App extends React.Component {
state = {
checkboxes: [
{ id: 1, checked: false, label: "a" },
{ id: 2, checked: false, label: "b" },
{ id: 3, checked: false, label: "c" }
]
};
handleChange = inputsType => (event, inputId) => {
const checked = event.target.checked;
// Functional update is recommended as the new state depends on the old state
this.setState(prevState => {
return {
[inputsType]: prevState[inputsType].map(iT => {
// if the ids match update the 'checked' prop
return inputId === iT.id ? { ...iT, checked } : iT;
})
};
});
};
render() {
console.log(this.state.checkboxes);
return (
<div>
{this.state.checkboxes.map(cb => (
<Checkbox
key={cb.id}
handleChange={this.handleChange("checkboxes")}
{...cb}
/>
))}
</div>
);
}
}

Conditionally disable React Checkbox

I am trying to conditionally disable the checkbox in react, based on the count. Passing the value through props whether it is checked and greater than the number. I am saving the name in the state to further process it to send to in the backend database.
Here is my react code.
class CheckboxComponent extends Component {
constructor(props) {
super(props);
this.state = {
checkedItems: {}
};
}
handleChange = (event, formKey) => {
const {checkedItems} = this.state;
const checkedValues = {...checkedItems};
checkedValues[event.target.name] = event.target.checked;
this.setState((prevState, currState) => {
return {
...prevState,
checkedItems: checkedValues
}
});
};
render = () => {
const {checkedItems} = this.state;
const checkedValues = {...checkedItems};
const checkedCount = Object.values(checkedValues).length;
const checked = Object.values(checkedValues);
const disabled = checkedCount >= 3;
return (
<div>
{checkboxes.map((item, index) => (
<label className={`form__field__input__label`} key={item.key}>
<Input
type={`checkbox`}
name={item.name}
checked={this.state.checkedItems[item.name] || false}
onChange={this.handleChange}
formKey={'subjects'}
disabled={(!checked[index] && checked.length > 3)}
/>
{item.name}
</label>
))}
</div>
)
This is the Array that I am passing to render the values in the checkbox
const checkboxes = [
{
name: "Math and economics",
key: "mathsandeconomics",
label: "Math and economics"
},
{
name: "Science",
key: "Science",
label: "Science"
},
The below code snippet will work fine for you. And you can sent object to the backend having maximum of only 3 properties set to true. Get the full code from codesandbox link https://codesandbox.io/s/emmeiwhite-0i8yh
import React from "react";
const checkboxes = [
{
name: "Math and economics",
key: "mathsandeconomics",
label: "Math and economics",
},
{
name: "Science",
key: "science",
label: "Science",
},
{
name: "history",
key: "history",
label: "history",
},
{
name: "literature",
key: "literature",
label: "literature",
},
];
class CheckboxComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
checkedItems: {},
count: 0,
};
}
handleChange = (event, formKey) => {
const { name, checked } = event.target;
const updatedCheckedItems = { ...this.state.checkedItems, [name]: checked };
this.setState({
checkedItems: updatedCheckedItems,
count: Object.values(updatedCheckedItems).filter((value) => value).length,
});
};
render = () => {
const checkedValues = { ...this.state.checkedItems };
const checkedCount = Object.values(checkedValues).filter((value) => value)
.length;
console.log(this.state.checkedItems);
return (
<div>
{checkboxes.map((item, index) => (
<label className={`form__field__input__label`} key={item.key}>
<input
type={`checkbox`}
name={item.name}
checked={this.state.checkedItems[item.name] || false}
onChange={this.handleChange}
disabled={!checkedValues[item.name] && checkedCount > 2}
/>
{item.name}
</label>
))}
</div>
);
};
}
export default CheckboxComponent;
Your checked.length counts all touched boxes, not checked only. If you uncheck an input, it still will be counted. Count only true, for example Object.values(checkedValues).filter(value => value).length.
Use names instead of indexes: disabled={!checkedValues[item.name] && checkedCount > 3}
You can see full solution here: https://codesandbox.io/s/confident-http-vlm04?file=/src/App.js
event.target.getAttribute('name');
try this to get name attribute, pretty sure event.target.name is 'undefined'
I see one use case is not taken care of. checkedCount should count the number of true values only.
const checkedCount = Object.values(checkedValues).length; // existing
const checkedCount = Object.values(checkedValues).filter(item=>item==true).length //replace with this line
This would solve the problem.
Here is the code and as well as codesandbox link
Codesandbox Link
import React from "react";
export class CheckboxComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
checkedItems: {},
checkedCount: 0
};
}
handleChange = (event, formKey) => {
const { checkedItems } = this.state;
const checkedValues = { ...checkedItems };
checkedValues[event.target.name] = event.target.checked;
this.setState((prevState, currState) => {
return {
...prevState,
checkedItems: checkedValues,
checkedCount: event.target.checked
? prevState.checkedCount + 1
: prevState.checkedCount - 1
};
});
};
render = () => {
const { checkboxes } = this.props;
const { checkedCount } = this.state;
const disabled = checkedCount >= 3;
return (
<div>
<p></p>
{checkboxes.map((item, index) => (
<label className={`form__field__input__label`} key={item.key}>
<input
type={`checkbox`}
name={item.name}
checked={this.state.checkedItems[item.name] || false}
onChange={this.handleChange}
disabled={!this.state.checkedItems[item.name] ? disabled : false}
/>
{item.name}
</label>
))}
</div>
);
};
}

update checkbox isChecked stauts in parent

Parent component
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
checkedView:[
{id: 1, value: "A", isChecked: false},
{id: 2, value: "B", isChecked: true},
{id: 3, value: "C", isChecked: true},
{id: 4, value: "D", isChecked: true}
],
}
}
handleCheck=(e)=>{
this.setState({ isChecked: e.target.checked});
}
render(){
return(
<div>
<Selection checkedView={this.state.checkedView} handleCheck={this.handleCheck} />
<Content checkedView={this.state.checkedView} />
</div>
);
}
}
Selection component
class Selection extends Component {
constructor(props) {
super(props)
this.state = {
checkedView: this.props.checkedView
}
}
handleCheck = (event) => {
let checkedView = this.props.checkedView;
checkedView.forEach( item => {
if(item.value === event.target.value){
item.isChecked = event.target.checked
}
})
this.setState({
checkedView: checkedView
})
this.props.handleCheck(event)
}
render() {
return (
<div className="">
<ul className="morefeatures">{
this.props.checkedView.map((selection, index) => {
return (<CheckBox key={index} handleCheck={this.handleCheck} {...selection} />)
})
}
</ul>
</div>
);
}
}
CHECKBOX
export const CheckBox = props => {
return (
<li>
<input key={props.id} onClick={props.handleCheck} type="checkbox" checked={props.isChecked} value={props.value} /> {props.value}
</li>
)
}
I have a parent component that controls the content, and the selection component should be able to update its change to the parent component so other child components then can access the value of the checkbox.
When a (or multiple) checkboxes are checked, Content Component receives signal to display relevant content, something like that
The isChecked not change its status no mater the checkbox is checked or not checked.
So how should I modify the code to make the isChecked really work?????
You are not updating the right state. this.setState({ isChecked: e.target.checked}); adds a new variable isChecked to the state. What you want is find the right entry within checkedView and update that object. I would pass down the id to the checkbox and on onClick, I would call handleChecked which receives not only the event but also the id of the checkbox. In handleChecked you can then find the right checkbox based on the id and update that one accordingly.
In parent:
handleCheck=(id, checked)=>{
// deep copy old state (check out lodash for a nicer deepCopy)
const checkedView = JSON.parse(JSON.stringify(this.state.checkedView));
const checkBox = checkedView.find(view => view.id === id);
checkBox.isChecked = checked;
// update whole object of new state
this.setState(checkedView);
}
In Selection:
!Warning! You were altering props, never update props, that's the job of the parent. You also don't need to put the checkedView in the state in Selection, you receive it as a prob, just pass it down.
handleCheck = (event, id) => {
this.props.handleCheck(id, e.target.checked)
}
class Selection extends Component {
constructor(props) {
super(props)
}
handleCheck = (event, id) => {
this.props.handleCheck(id, e.target.checked)
}
render() {
return (
<div className="">
<ul className="morefeatures">{
this.props.checkedView.map((selection) => {
return (<CheckBox key={selection.id} handleCheck={this.handleCheck} {...selection} />)
})
}
</ul>
</div>
);
}
}
In Checkbox, wrap the handleCheck to pass it both the event and the id to identify the checkbox.
<input onClick={(e) => props.handleCheck(e, props.id)} type="checkbox" checked={props.isChecked} value={props.value} /> {props.value}
If I understand your question I think this is what you're looking for - your architecture and logic are a bit off and you've coded yourself into a corner...- Two components (Parent and Child)
Here is your Parent: (keeps the state and all methods that manipulate it)
export default class Parent extends React.Component {
state = {
checkedView: [
{ id: 1, value: 'A', isChecked: false },
{ id: 2, value: 'B', isChecked: true },
{ id: 3, value: 'C', isChecked: true },
{ id: 4, value: 'D', isChecked: true }
]
};
handleCheck = (id) => {
this.setState({
checkedView: this.state.checkedView.map((item) => {
if (item.id === id) {
return {
...item,
isChecked: !item.isChecked
};
} else {
return item;
}
})
});
};
render() {
return (
<div>
{this.state.checkedView.map((item) => (
<Child key={item.id} item={item} handleCheck={this.handleCheck} />
))}
</div>
);
}
}
Here is your Child:
import React from 'react';
export default function Child({ item, handleCheck }) {
return (
<div onClick={() => handleCheck(item.id)}>
{item.value}
<input type='checkbox' defaultChecked={item.isChecked} />
</div>
);
}
Here is a live demo: https://stackblitz.com/edit/react-ddqwpb?file=src%2FApp.js
I'm guessing you can understand what the code is doing... if not ask...

Check all checkboxes, and uncheck if any of them deselected in React without jQuery

I would like to select all checkboxes. But if user deselect any checkbox, i want also uncheck "select all" checkbox.
In given example below, How can achieve this?
Live Demo: https://react-f3tcbc.stackblitz.io
Live Editor: https://stackblitz.com/edit/react-f3tcbc
Some samples I looked refer checked as boolean parameter in items. However my items object comes from ajax response as json and they have no value such as checked.
I want to do this in React way. Not jQuery. Any ideas?
One way of going about it is to add an extra property called e.g. isChecked to the data you get from the network request, and use that to control all the
checkboxes.
Example
const posts = [
{
id: 1,
name: "Text 1"
},
{
id: 2,
name: "Text 2"
},
{
id: 3,
name: "Text 3"
}
];
class App extends React.Component {
state = {
name: "React",
posts: [],
isAllChecked: false
};
componentDidMount() {
setTimeout(() => {
this.setState({
posts: posts.map(post => ({ ...post, isChecked: false }))
});
}, 1000);
}
handleSelect = id => {
this.setState(prevState => {
const posts = prevState.posts.map(post =>
post.id === id ? { ...post, isChecked: !post.isChecked } : post
);
const isAllChecked = posts.every(post => post.isChecked);
return { posts, isAllChecked };
});
};
handleSelectAll = () => {
this.setState(prevState => {
const isAllChecked = !prevState.isAllChecked;
const posts = prevState.posts.map(post => ({
...post,
isChecked: isAllChecked
}));
return { posts, isAllChecked };
});
};
render() {
const { posts, isAllChecked } = this.state;
return (
<div>
{posts.map(fx => (
<TableItem
key={fx.id}
id={fx.id}
name={fx.name}
checked={fx.isChecked}
onChange={() => this.handleSelect(fx.id)}
/>
))}
<div>
<label>
<input
type="checkbox"
checked={isAllChecked}
onChange={this.handleSelectAll}
/>
Select all
</label>
</div>
</div>
);
}
}
class TableItem extends React.Component {
render() {
const { checked, onChange, name } = this.props;
return (
<tr>
<td>
<input type="checkbox" checked={checked} onChange={onChange} />
</td>
<td>{name}</td>
</tr>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Removing from react array

How do I remove an item from an array in react? I've tried a couple of things and it didnt work out. Just trying to make a basic todo app. I've updated my post to provide the render method to show where the deleteTodo is going. I've also updated my deleteTodo with an answer I got from this post. It kind of works, the only problem is it deletes all of the items in the todo list rather than just the single one.
class App extends Component {
state = {
inputValue: "",
todos: [{
value: "walk the dog",
done: false
},
{
value: "do the dishes",
done: false
}
]
}
addTodo = (e) => {
this.setState({
inputValue: e.target.value
});
}
handleSubmit = (e) => {
e.preventDefault();
// console.log('clicked')
const newTodo = {
value: this.state.inputValue,
done: false
}
const todos = this.state.todos;
todos.push(newTodo);
this.setState({
todos,
inputValue: ''
})
}
deleteTodo = (value) => {
// Take copy of current todos
const todos = [this.state.todos];
const filteredTodos = todos.filter((item) => item.value !== value);
this.setState({
todos: filteredTodos
})
}
render() {
return (
<div className="App">
<Form
addTodo={this.addTodo}
handleSubmit={this.handleSubmit}
/>
<List
todos={this.state.todos}
deleteTodo={this.deleteTodo}
/>
</div>
);
}
}
export default App;
You just need to filter that value from array and set new filtered array in the setState.
deleteTodo = (value) => {
// Take copy of current todos
const todos = [...this.state.todos];
const filteredTodos = todos.filter( (item) => item.value !== value);
this.setState({
todos: filteredTodos
})
}
Your use of filter appears to be the problem.. To create a new array of elements without the value using filter, you can try something like this:
EDIT: Updated answer with a full working demo
import React, {Component} from 'react';
import './App.css';
class Form extends Component {
constructor(props) {
super(props);
this.textRef = React.createRef();
}
render() {
return (
<form onSubmit={(e)=>{e.preventDefault(); this.props.handleSubmit(this.textRef.current.value)}}>
<input type="text" ref={this.textRef}/>
<input type="submit" value="add"/>
</form>
);
}
}
class List extends Component {
render() {
return (
<ul>
{
this.props.todos.map((todo) => (
<li key={todo.value}>
<p><input type="checkbox" checked={todo.done}/>{todo.value} </p>
<input type="button" onClick={() => (this.props.deleteTodo(todo.value))} value="delete"/>
</li>
))
}
</ul>
);
}
}
class App extends Component {
state = {
inputValue: "",
todos: [{
value: "walk the dog",
done: false
},
{
value: "do the dishes",
done: false
}
]
}
addTodo = (e) => {
this.setState({
inputValue: e.target.value
});
}
handleSubmit = (value) => {
const newTodo = {
value,
done: false
}
const todos = [...this.state.todos];
todos.push(newTodo);
this.setState({
todos,
inputValue: ''
})
}
deleteTodo = (value) => {
const todos = this.state.todos;
const filteredTodos = todos.filter((item) => item.value !== value);
this.setState({
todos: filteredTodos
})
}
render() {
return (
<div className="App">
<Form
addTodo={this.addTodo}
handleSubmit={this.handleSubmit}
/>
<List
todos={this.state.todos}
deleteTodo={this.deleteTodo}
/>
</div>
);
}
}
export default App;
Also, I changed your handleSubmit method to create a new Array to keep in line with React's functional paradigm
i have used lodash for such this.
lodash is a library for doing such thing https://lodash.com/
if you can get the same value object that you inserted while adding the rest is quite easy
you lodash to find the index in which you have the object in the array
on your delete function
const todos = this.state.todos;
const itemToRemove = {value: "walk the dog",done: false};
var index = _.findIndex(todos, itemToRemove);
const filteredTodos = todos.splice(index, 1)
this.setState({
todos: filteredTodos
})
Hope this will help

Categories