Conditionally disable React Checkbox - javascript

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

Related

advice with removing object from react state array

So this has me puzzled. I've been banging my head against the wall trying to figure this out.
So I am trying to remove an object from a state managed array. I don't believe I am mutating the array.
I am using prevState. My delete function which gets sent to another component
{this.state.educationArray.map((item, i) => (<RenderEducation education={item} onDelete={this.handleDelete} />))}
Sending back the id to the handleDelete function.
My handleDelete:
handleDelete = itemId => {
//const newStudy = this.state.educationArray.filter(item => { return item.id !== itemId });
//this.setState({ educationArray: newStudy })
let tempArray = [];
let num = this.state.educationArray.length;
for (let i = 0; i < num;) {
//console.log("itemId is: ", itemId)
let tempId = this.state.educationArray[i].id
if (tempId != itemId) {
let obj = this.state.educationArray[i]
tempArray.push(obj)
}
i++
}
this.setState(prevState => ({ educationArray: tempArray }));
}
Stack Snippet w/loop:
const { useState } = React;
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
educationArray: [
{ id: 1, name: "One" },
{ id: 2, name: "Two" },
{ id: 3, name: "Three" },
],
};
}
handleDelete = (itemId) => {
// const newStudy = this.state.educationArray.filter(item => { return item.id !== itemId });
// this.setState({ educationArray: newStudy })
let tempArray = [];
let num = this.state.educationArray.length;
for (let i = 0; i < num; ) {
//console.log("itemId is: ", itemId)
let tempId = this.state.educationArray[i].id;
if (tempId != itemId) {
let obj = this.state.educationArray[i];
tempArray.push(obj);
}
i++;
}
this.setState((prevState) => ({ educationArray: tempArray }));
};
render() {
return (
<ul>
{this.state.educationArray.map((element) => (
<li key={element.id}>
{element.name}{" "}
<input type="button" value="Del" onClick={() => this.handleDelete(element.id)} />
</li>
))}
</ul>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Stack Snippet w/filter:
const { useState } = React;
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
educationArray: [
{ id: 1, name: "One" },
{ id: 2, name: "Two" },
{ id: 3, name: "Three" },
],
};
}
handleDelete = (itemId) => {
const newStudy = this.state.educationArray.filter(item => { return item.id !== itemId });
this.setState({ educationArray: newStudy })
/*
let tempArray = [];
let num = this.state.educationArray.length;
for (let i = 0; i < num; ) {
//console.log("itemId is: ", itemId)
let tempId = this.state.educationArray[i].id;
if (tempId != itemId) {
let obj = this.state.educationArray[i];
tempArray.push(obj);
}
i++;
}
this.setState((prevState) => ({ educationArray: tempArray }));
*/
};
render() {
return (
<ul>
{this.state.educationArray.map((element) => (
<li key={element.id}>
{element.name}{" "}
<input type="button" value="Del" onClick={() => this.handleDelete(element.id)} />
</li>
))}
</ul>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
I've tried using the 2 lines commented out, I've tried rearranging how I do the for loop, its always the same result, it never removes the intended id.
I have sent console.log after console.log of the ids getting moved around and every seems to be working, but when it comes right now to push the specific objects that don't match the id to the temp array it never works and the object add the end gets removed.
Please and thank you for your advice
EDIT:
i call the handleDelete inside RenderEducation component:
<button onClick={() => this.props.onDelete(this.state.id)}> X - {this.state.id}</button>
from each
and my constructor:
constructor(props) {
super(props);
this.state = {
educationArray: [],
}
}
and how i add to the array:
addEducation = (e) => {
e.preventDefault();
this.setState(prevState => ({
educationArray: [...prevState.educationArray, {
id: uniqid(),
school: '',
study: '',
dateFrom: '',
dateTo: '',
editing: true,
}]
}))
}
Both versions of your code work in regular, non-edge-case situations, as we can see from the Stack Snippets I added to your question. The only problem I can see with the code shown is that it's using a potentially out-of-date version of the educationArray. Whenever you're updating state based on existing state, it's best to use the callback form of the state setter and use the up-to-date state information provided to the callback. Both of your versions (even your loop version, which does use the callback) are using this.state.educationArray instead, which could be out of date.
Instead, use the array in the state passed to the callback:
handleDelete = (itemId) => {
// Work with up-to-date state via the callback
this.setState(({educationArray: prevArray}) => {
// Filter out the element you're removing
return {
educationArray: prevArray.filter(({id}) => id !== itemId)
};
});
};
Live Example:
const { useState } = React;
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
educationArray: [
{ id: 1, name: "One" },
{ id: 2, name: "Two" },
{ id: 3, name: "Three" },
],
};
}
handleDelete = (itemId) => {
// Work with up-to-date state via the callback
this.setState(({educationArray: prevArray}) => {
// Filter out the element you're removing
return {
educationArray: prevArray.filter(({id}) => id !== itemId)
};
});
};
render() {
return (
<ul>
{this.state.educationArray.map((element) => (
<li key={element.id}>
{element.name}{" "}
<input type="button" value="Del" onClick={() => this.handleDelete(element.id)} />
</li>
))}
</ul>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

How to customize Ant table rowselection

I used Ant table to show some information.
https://codesandbox.io/s/proud-architecture-lsb85?file=/src/index.js
I want to customize the position of the checkbox for row selection.
In this application, you can see the header in the following order of checkbox, Name, Age, Address but I want to swap checkbox and Name.
You can add checkbox columns and customize render and titleRender of it to checkbox and then handle the events. if you incounter performance issue you have to add some memoization on columns or evenet handlers.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import { Table, Button, Checkbox } from "antd";
const data = [];
for (let i = 0; i < 46; i++) {
data.push({
key: i,
name: `Edward King ${i}`,
age: 32,
address: `London, Park Lane no. ${i}`
});
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedRowKeys: [], // Check here to configure the default column
loading: false,
allChecked: false
};
this.columns = [
{
title: "Name",
dataIndex: "name"
},
{
dataIndex: "checked",
title: () => {
return (
<Checkbox
checked={this.state.allChecked}
onChange={(e) => this.selectAll(e)}
></Checkbox>
);
},
render: (text, rec, index) => {
return (
<Checkbox
checked={
this.state.selectedRowKeys.includes(rec.key) ||
this.state.allChecked
}
onChange={(e) => this.onChange(e, rec)}
></Checkbox>
);
}
},
{
title: "Age",
dataIndex: "age"
},
{
title: "Address",
dataIndex: "address"
}
];
}
start = () => {
this.setState({ loading: true });
// ajax request after empty completing
setTimeout(() => {
this.setState({
selectedRowKeys: [],
loading: false
});
}, 1000);
};
onChange = (e, rec) => {
const checked = e.target.checked;
if (checked) {
this.setState((state) => ({
...state,
selectedRowKeys: [...state.selectedRowKeys, rec.key]
}));
} else {
this.setState((state) => ({
...state,
selectedRowKeys: [
...state.selectedRowKeys.filter((item) => item !== rec.key)
]
}));
}
};
selectAll = (e) => {
const checked = e.target.checked;
if (checked) {
this.setState((state) => ({
...state,
allChecked: true
}));
} else {
this.setState((state) => ({
...state,
allChecked: false
}));
}
};
onSelectChange = (selectedRowKeys) => {
console.log("selectedRowKeys changed: ", selectedRowKeys);
this.setState({ selectedRowKeys });
};
render() {
const { loading, selectedRowKeys } = this.state;
const hasSelected = selectedRowKeys.length > 0;
return (
<div>
<div style={{ marginBottom: 16 }}>
<Button
type="primary"
onClick={this.start}
disabled={!hasSelected}
loading={loading}
>
Reload
</Button>
<span style={{ marginLeft: 8 }}>
{hasSelected ? `Selected ${selectedRowKeys.length} items` : ""}
</span>
</div>
<Table columns={this.columns} dataSource={data} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));

How to toggle between one checkbox and a whole group of checkboxes?

My purpouse here is to create a group of checboxes. "Search everywhere" is default checked, if you check something else "Search everywhere" automatically unchecked, you can check as many different checkboxes as you want, until you check "search everywhere" again, if you do that all other checkboxes will unchecked.
I want to create it in function component with Hooks in React.
View: Image how it looks in browser
Everything is ready, but I stuck a little bit with toggle between one checkbox and group of checkboxes. I've tried useState and useEffect to controll useState callback. Thanks for help.
const ButtonCategory = (props) => {
const [state, setState] = useState({
normalCheckbox: false,
specialCheckbox: true
});
const { id, name, special, products } = props;
const toggleOthers = () => {
if (state.specialCheckbox) {
setState({
...state,
normalCheckbox: false // ofc its bad
});
} else if (state.normalCheckbox) {
setState({
...state,
specialCheckbox: false // ofc its bad
});
}
};
const toggleNormal = () => {
setState({
...state,
normalCheckbox: !state.normalCheckbox
});
};
const toggleSpecial = () => {
setState({
...state,
specialCheckbox: !state.specialCheckbox
});
};
useEffect(() => {
toggleOthers();
}, [state.specialCheckbox, state.normalCheckbox]);
return (
<>
<Label>
<StyledInput
type="checkbox"
id={id}
checked={special ? state.specialCheckbox : state.normalCheckbox}
onChange={special ? () => toggleSpecial() : () => toggleNormal()}
onClick={(e) => {
/* do something */
}}
/>{" "}
<div>
{" "}
{name} {special ? null : `(${products})`}
</div>
</Label>
</>
);
};
I believe you want something like this:
import React, { useState } from "react";
export const Checkboxes = () => {
const [checkedIds, setCheckedIds] = useState(new Set(["everywhere"]));
const handleCheck = ({ id, checked }) => {
if (checked) {
if (id === "everywhere") {
checkedIds.clear();
} else {
checkedIds.delete("everywhere");
}
checkedIds.add(id);
} else {
checkedIds.delete(id);
}
setCheckedIds(new Set(checkedIds));
};
return (
<form>
<label>
<input
id="everywhere"
type="checkbox"
checked={checkedIds.has("everywhere")}
onChange={(e) => handleCheck(e.target)}
/>{" "}
Search everywhere
</label>
<label>
<input
id="option-1"
type="checkbox"
checked={checkedIds.has("option-1")}
onChange={(e) => handleCheck(e.target)}
/>{" "}
Option 1
</label>
<label>
<input
id="option-2"
type="checkbox"
checked={checkedIds.has("option-2")}
onChange={(e) => handleCheck(e.target)}
/>{" "}
Option 2
</label>
</form>
);
};
Test case at codesandbox.io
May be this could be helpful
import React from "react";
import "./style.css";
export const App = () => {
const _checkboxes = [
{
id: "id1",
name: "111",
value: "111",
label: "111",
checked: true
},
{
id: "id2",
name: "222",
value: "222",
label: "222",
checked: false
},
{
id: "id3",
name: "333",
value: "333",
label: "333",
checked: false
}
];
const [checkboxes, setCheckboxes] = React.useState(_checkboxes);
const handleChange = id => e => {
setCheckboxes(checkboxes => {
const firstId = "id1";
const temp = checkboxes.map(c => {
if (firstId === id) {
c.checked = c.id === firstId ? !c.checked : false;
} else {
if (c.id === id) {
c.checked = !c.checked;
} else {
if (c.id === firstId) {
c.checked = false;
}
}
}
return c;
});
return [...temp];
});
};
return (
<div>
{checkboxes.map(checkbox => (
<div key={checkbox.id}>
<input
type="checkbox"
onChange={handleChange(checkbox.id)}
value={checkbox.value}
name={checkbox.name}
id={checkbox.id}
checked={checkbox.checked}
/>
<label htmlFor={checkbox.id}>{checkbox.label}</label>
</div>
))}
</div>
);
};
https://stackblitz.com/edit/react-rtxxfp?file=src%2FApp.js

How to create an array based on multiple inputs

I am trying create an array with some objects in it, Im trying to gather the data from multiple inputs. I am creating a restaurant Menu, where I will have different titles such as Breakfasts, Entrees... and under each title I will have different plates.
Im trying to create an array like this:
menu: [
[ 'Lunch',
[{plate: 'Rice and Beans', description: 'Rice and Beans for Lunch', price: 50.49 }]
]
[ 'Dinner',
[{plate: 'Some Dinner', description: 'Dinner Description', price: 35.49 }]
]
]
The question is, how do I add first a Title, and under that title how do I add plates?
I also wanted to know how to make it, so I made it for practice. I hope it helps.
import React from 'react';
class MenuInput extends React.Component {
render() {
const {id, handleInput} = this.props;
return (
<div>
Title : <input name="title" onChange={(e) => handleInput(id, e)}/>
Plate : <input name="plate" onChange={(e) => handleInput(id, e)}/>
Description : <input name="description" onChange={(e) => handleInput(id, e)}/>
Price : <input name="price" onChange={(e) => handleInput(id, e)}/>
</div>
)
}
}
export default class Menu extends React.Component {
state = {
inputCount: 1,
inputData: [[]],
result: []
}
saveData = (e) => {
const {inputData, result} = this.state;
inputData.forEach(input => {
const {title, plate, description, price} = input;
const findInputIndex = result.findIndex(data => data.indexOf(title) >= 0);
if (findInputIndex >= 0) {
const [menuName, menuList] = result[findInputIndex];
result[findInputIndex] = [menuName, [...menuList, {plate, description, price}]]
} else {
result.push([title, [{plate, description, price}]])
}
});
this.setState({
result
})
}
handleInput = (id, e) => {
const {name, value} = e.target;
const {inputData} = this.state;
inputData[id] = {...inputData[id], [name]: value};
this.setState({
inputData
})
}
addInput = () => {
const {inputCount, inputData} = this.state;
this.setState({
inputCount: inputCount + 1,
inputData: [...inputData, []]
})
};
getInputList = () => {
const {inputCount} = this.state;
let inputList = [];
for (let i = 0; i < inputCount; i++) {
inputList.push(<MenuInput id={i} key={i} handleInput={this.handleInput}/>)
}
return inputList
}
render() {
const {result} = this.state;
console.log(result)
return (
<div>
{this.getInputList()}
<button onClick={this.addInput}>Add Plate</button>
<br/>
<button onClick={this.saveData}>save</button>
{
result.length > 0 && result.map(res => {
const [menuName, menuList] = res;
return (
<div key={menuName}>
<strong>Title : {menuName}</strong>
{menuList.map(menu => {
const {plate, description, price} = menu;
return(
<div key={plate}>
<span style={{marginRight : '10px'}}>plate : {plate}</span>
<span style={{marginRight : '10px'}}>description : {description}</span>
<span>price : {price}</span>
</div>
)
})}
</div>
)
})
}
</div>
)
}
}

Select/Unselect All checkbox in reactJs

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.

Categories