Limit selection of checkboxes react - reactjs

What should I do if I would like to limit the selected checkboxes to 3, which you cannot select more than 3 checkboxes. By referring this link https://codesandbox.io/s/wild-silence-b8k2j?file=/src/App.js
How to add some code to limit the selection here?
I am trying to play around this but didn’t figure out

const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
// allow only 3 elements
if (updatedCheckedState.filter(v => v).length >= 4) {
return
}
setCheckedState(updatedCheckedState);
const totalPrice = updatedCheckedState.reduce(
(sum, currentState, index) => {
if (currentState === true) {
return sum + toppings[index].price;
}
return sum;
},
0
);
setTotal(totalPrice);
};

Try this, you need to check length of selected array and checked status of current input
const handleOnChange = (position, e) => {
if (checkedState.filter((i) => i).length >= 3 && e.target.checked) return;
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
setCheckedState(updatedCheckedState);
const totalPrice = updatedCheckedState.reduce((sum, currentState, index) => {
if (currentState === true) {
return sum + toppings[index].price;
}
return sum;
}, 0);
setTotal(totalPrice);
};
<input
type="checkbox"
id={`custom-checkbox-${index}`}
name={name}
value={name}
checked={checkedState[index]}
onChange={(e) => handleOnChange(index, e)}
/>
codesandbox

Let's start from the state array...
As argument, use a function instead a direct value. It works the same, but it prevents to recreate uselessly an array (then fill it), which will be trashed on every render.
const [checkedState, setCheckedState] = useState(
() => new Array(toppings.length).fill(false)
);
Then, count how many are currently selected:
const selectedCount = checkedState.reduce((sum, currentState, index) => {
if (currentState === true) {
return sum + 1;
}
return sum;
}, 0);
Finally, prevent further selections on the checkboxes which are still selectable:
<input
type="checkbox"
id={`custom-checkbox-${index}`}
name={name}
value={name}
checked={checkedState[index]}
onChange={(e) => handleOnChange(index, e)}
disabled={!checkedState[index] && selectedCount >= 3}
/>

Related

React retrieve checked value in checkbox

I am trying to retrieve the checked values of the checkboxes and save them into array.
I tried :
arr.push(setNewItem(checked))
arr.push(e.target.value.checked)
arr.push(items.checked)
But these return type error or undefined values.
const [checkedItems, setCheckedItems] = useState([]);
const handleChange = (e) => {
if (e.target.checked) {
var arr = [...checkedItems];
//arr.push(setNewItem(e.target.value.checked));
setCheckedItems(arr);
console.log(arr);
} else {
checkedItems = "";
}
setIsChecked((current) => !current);
};
return (
<div className="App">
<StyleForm>
<StyleInput
type="text"
placeholder="Add"
value={newItem}
onChange={(e) => setNewItem(e.target.value)}
onKeyPress={handleOnKeyPress}
/>
<ButtonAddStyle onClick={() => addItem()}>add</ButtonAddStyle>
<StyleUl>
{items.map((item) => {
return (
<StyleLi key={item.id}>
<StyleCheckBox
type="checkbox"
value={isChecked}
onChange={handleChange}
/>
{item.value}
{""}
<ButtonDelStyle onClick={() => deleteItem(item.id)}>
X
</ButtonDelStyle>
</StyleLi>
);
})}
</StyleUl>
</StyleForm>
</div>
);
arr.push(e.target.checked);
Is the way to go and get rif of :
else {
checkedItems = "";
}
you cannot update a hook this way you will get an error when you try to unchek an input:
Uncaught TypeError : Assignment to constant variable
Now let's see what you are trying to do you are storing e.target.checked each time an input is cheked so checkedItems will look something like this :
[true, true, true, true, true, true, true, true]
why do you need this ? better is to store the ids of checked items :
const handleChange = (isChecked, id) => {
var arr = [...checkedItems];
if (isChecked) {
arr.push(id);
setCheckedItems(arr);
} else {
setCheckedItems(checkedItems.filter((storedId) => storedId !== id)); // delete the id from checkedItems if the corresponding input is unckecked
}
};
and from jsx :
<StyleCheckBox
type="checkbox"
value={item.id}
onChange={(e) => {
handleChange(e.target.checked, item.id);
}}
/>;
Now look at this :
<StyleCheckBox
value={isChecked} // this line
>
you are mapping through items creating multiple checkBoxes but all of them share the same value. and the value attribute of an input of type checkbox is not what you think it is, learn more here. so you can use value={item.id} to have an unique value for each input and get rid of isChecked useState hook you really don't need it
this could solve your problem.
const [checkedItems, setCheckedItems] = useState([]);
const handleChange = (e) => {
setCheckedItems( prev => [...prev, e.target.checked]);
setIsChecked((current) => !current);
};

How to put the last checkbox in useState in React?

I am developing an application on React and I needed to add product checkboxes to the form. Everything works correctly except that the last action (marking or removing the checkbox) does not go to the server (the error is not in the server, I tested it separately). Here is the output of the checkboxes:
<Form.Group className="mb-3">
<Form.Label>Товары</Form.Label>
{prodsl.map((emp, index) =>
<div className="mb-3">
<Form.Check
type='checkbox'
value={emp.id}
name='pp'
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
{emp.name}
</div>
)}
</Form.Group>
And here is the handler (checkedState - an array of checkbox states):
const [prods, setProd] = useState([])
const [checkedState, setCheckedState] = useState(
new Array(555).fill(false)
);
const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
setCheckedState(updatedCheckedState);
let arr = []
const updatedProd = checkedState.map((item, index) => {
if (item) {
arr = [...arr, index]
}
}
);
setProd(arr);
};
Submit form handler:
const newOrder = async () => {
const response = await addOrder(prods, client, emp, d1, d2)
stat++;
}
I tried to do a separate check before sending, I tried to call the function before sending it to a random checkbox so that this change would be the last and would not be counted.
Set states in react are async. It means that your values are not updated immediately after your setstate.
In order to fix the issue, you have 2 options.
Use the updatedCheckedState variable to set your prod:
const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
setCheckedState(updatedCheckedState);
let arr = []
const updatedProd = updatedCheckedState.map((item, index) => {
if (item) {
arr = [...arr, index]
}
}
);
setProd(arr);
};
Move your logic into useEffect:
const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
setCheckedState(updatedCheckedState);
};
useEffect(() => {
let arr = []
const updatedProd = checkedState.map((item, index) => {
if (item) {
arr = [...arr, index]
}
});
setProd(arr);
}, [checkedState]);
And as a suggestion, use arr.push(index) instead of arr = [...arr, index];

How to get a value from onChange in react.js?

I'm trying to get value from onChange using setState but for some reason when I write text on input I get an error like Axis.map is not a function
Also,I'd like to delete Axisdata singly from the last one using splice or pop but whenever I click the delete button the Axis data disappeared except the first one.
Set Elements
const SetElements = ({
...
}) => {
const [Axis, setAxis] = useState([]);
const AxisHandler = e => {
setAxis([
...Axis,
{
label: "",
data: "",
backgroundColor: "",
},
]);
};
const deleteAxis = () => {
setAxis(Axis.splice(-1, 1));
};
return (
<>
<button onClick={AxisHandler}>add Line</button>
{Axis.length !== 1 && (
<button onClick={deleteAxis}>delete Line</button>
)}
{Axis.map((element, index) => (
<>
<AppendingAxis
Axis={Axis}
setAxis={setAxis}
element={element}
index={index}
/>
</>
))}
</>
)
AppendingAxis
const AppendingAxis = ({
index,
setAxis,
Axis,
}) => {
console.log(Axis);
return (
<AxisSetting>
<h4>{index + 2}Y Axis setting</h4>
<span>
<input
placeholder={index + 2 + "setting"}
type="textarea"
onChange={e => setAxis((Axis[index].label = e.target.value))}
/>
</span>
The issue is state mutation in the AppendingAxis component.
onChange={e => setAxis((Axis[index].label = e.target.value))}
You should shallow copy state, and nested state, then update specific properties.
onChange={e => setAxis(Axis => Axis.map((el, i) => i === index
? {
...el,
label: e.target.value
}
: el,
)}
I'm not a fan of passing the state updater function on to children as this make it the child component's responsibility to maintain your state invariant. I suggest moving this logic into the parent component so it can maintain control over how state is updated.
SetElements parent
const changeHandler = index => e => {
const { value } = e.target;
setAxis(Axis => Axis.map((el, i) => i === index
? {
...el,
label: value
}
: el,
);
};
...
<AppendingAxis
Axis={Axis}
onChange={changeHandler(index)}
/>
AppendingAxis child
const AppendingAxis = ({ Axis, onChange }) => {
console.log(Axis);
return (
<AxisSetting>
<h4>{index + 2}Y Axis setting</h4>
<span>
<input
placeholder={index + 2 + "setting"}
type="textarea"
onChange={onChange}
/>
</span>
And for completeness' sake, your delete handler looks to also have a mutation issue.
const deleteAxis = () => {
setAxis(Axis.splice(-1, 1));
};
.splice mutates the array in-place and returns an array containing the deleted elements. This is quite the opposite of what you want I think. Generally you can use .filter or .slice to generate new arrays and not mutate the existing one.
const deleteAxis = () => {
setAxis(Axis => Axis.slice(0, -1)); // start at 0, end at second to last
};
This is happening because of this line:
onChange={e => setAxis((Axis[index].label = e.target.value))}
Create a function:
const handleAxisChange = (e, index) => {
Axis[index].label = e.target.value;
setAxis(new Array(...Axis));
}
And then change set the onChange like this:
onChange={e => handleAxisChange(e, index)}
Your problem is because of you don't mutate state correctly. You should make a shallow copy of the state. You can change AppendingAxis to this code:
const AppendingAxis = ({
index,
setAxis,
Axis,
}) => {
console.log(Axis);
const onChange = (e,index)=>{
let copy = [...Axis];
copy[index].label = e.target.value;
setAxis(copy);
}
return (
<AxisSetting>
<h4>{index + 2}Y Axis setting</h4>
<span>
<input
placeholder={index + 2 + "setting"}
type="textarea"
onChange={e => onChange(e,index))}
/>
</span>

How to implement input auto-tab in React (focus on next input element on keyup event)?

Using React as library, I have several blocks of input text, each one with maxlength=1, and I would like to implement a function that everytime a character is entered in an input box the focus goes to the next one.
This is the list of input elements I'm talking about:
And this is a minimal representation on CodesSandbox: https://codesandbox.io/s/react-autotab-6kewb.
How can I get the desired behaviour in React?
This is the relevant snippet:
const autoTab = e => {
const BACKSPACE_KEY = 8;
const DELETE_KEY = 46;
if (e.keyCode === BACKSPACE_KEY) {
// TODO: implement backwards autoTab
} else if (e.keyCode !== DELETE_KEY) {
// TODO: implement forwards autoTab
}
};
const blocks = Array.from({ length: 10 }, (element, index) => (
<input className="block" key={index} maxLength={1} onKeyUp={autoTab} />
));
I have added tab indexes to your input elements and used them to navigate between items.
const autoTab = e => {
const BACKSPACE_KEY = 8;
const DELETE_KEY = 46;
let tabindex = $(e.target).attr("tabindex") || 0;
tabindex = Number(tabindex);
if (e.keyCode === BACKSPACE_KEY) {
tabindex -= 1;
} else if (e.keyCode !== DELETE_KEY) {
tabindex += 1;
}
const elem = $("[tabindex=" + tabindex + "]");
if (elem[0]) {
elem.focus();
}
};
const blocks = Array.from({ length: 10 }, (element, index) => (
<input
className="block"
tabIndex={index}
key={index}
maxLength={1}
onKeyUp={autoTab}
/>
));
EDIT: Here is a new way using refs and the Demo based on your code sandbox.
const autoTab = e => {
const BACKSPACE_KEY = 8;
const DELETE_KEY = 46;
let tabindex = $(e.target).attr("data-index") || 0;
tabindex = Number(tabindex);
let elem = null;
if (e.keyCode === BACKSPACE_KEY) {
elem = tabindex > 0 && elemRefs[tabindex - 1];
} else if (e.keyCode !== DELETE_KEY) {
elem = tabindex < elemRefs.length - 1 && elemRefs[tabindex + 1];
}
if (elem) {
elem.current.focus();
}
};
const Input = props => {
const ref = React.createRef();
elemRefs.push(ref);
return (
<input
className="block"
data-index={props.index}
ref={ref}
maxLength={1}
onKeyUp={props.autoTab}
/>
);
};
const blocks = Array.from({ length: 10 }, (element, index) => (
<Input key={index} index={index} autoTab={autoTab} />
));
const App = () => <div className="App">{blocks}</div>;
You have to use ref to focus on next element, you can use index along with your ref name to distinguish element
like this:
this.refs[yourrefname].nextSibling.focus();
and your input element should have ref with unique name
<input ref={yourrefname + index} className="block" key={index} maxLength={1} onKeyUp={autoTab} />
I solved it by creating a simple CSS selector
const autoTab = (e: ChangeEvent<HTMLInputElement>) => {
if (`${e.target.value.length}` === e.target.getAttribute("maxlength")) {
var inputs = document.getElementsByClassName("autotab");
for (let i = 0; i < inputs.length; i++) {
if (e.target == inputs[i]) {
i++;
if (i < inputs.length) {
let next: any = inputs[i];
next.focus();
}
}
}
}
};
return (
<>
<div>
<input
className="autotab"
type="text"
maxLength={1}
onChange={(e) => {
autoTab(e);
}}
/>
</>
);

How to clear fields array in redux form

I am trying to clear checkbox array in fields array from the redux form. How can I do it?
I'm using redux form FieldArray in the format of members:[filterOption:{}, checkBoxOption:{}]. checkBoxOption value depends o filterOption dropdown. Whenever the user selects an option from filterOption in result they get a list of checkbox from which they have to select from the list of checkBoxOption.
Let's say if a user has selected a value from filterOption and checkBoxOption and now they change the value of filterOption in result they will get a new list of an array for checkBoxOption. The values are getting replaced by the new one but they are not getting uncheck.
I am able to clear checkbox array in values array by using fields.get(event).checkBoxOption = {} but unable to find the solution on how to empty fields array.
Can anyone help me out with this?
<ul className="list-group">
{
fields.map((member, index) => (
<li className="list-group filter-select-box" key={index}>
<SelectBox
name={`${member}.filterOption`}
label="Metadata Attribute"
options={attributes.map(attribute => ({
value: attribute.name,
label: attribute.name,
disabled: selectedAttributes.filter(value => value.name === attribute.name).length > 0,
}))}
isChange
handleSelectChange={opt => handleOptionChange(index, opt.value)}
/>
{checkStatus(index) && (
<div className="select-checkbox-option form-group">
{
getCheckboxList(index).map((checkboxItem, x) => (
<CheckBox
key={x}
type="checkbox"
name={`${member}.checkBoxOption.${checkboxItem}`}
label={checkboxItem}
value={`${member}.checkBoxOption.${checkboxItem}`}
id={checkboxItem}
/>
))
}
</div>
)}
</li>
))
}
<li className="list-group filter-select-box">
<button className="add-filter" type="button" onClick={() => fields.push({})}>
<img src={filterPlus} alt="" className="filterPlus" />
Add Filter
</button>
{touched && error && <span>{error}</span>}
</li>
</ul>
the function which is getting checkbox value
const handleOptionChange = (event, nameAttribute) => {
const value = {
index: event,
status: true,
name: nameAttribute,
};
let selectedAttributesStatus = false;
for (let i = 0; i < selectedAttributes.length; i += 1) {
if (value.index === selectedAttributes[i].index) {
selectedAttributes[i].name = value.name;
selectedAttributesStatus = true;
}
}
if (!selectedAttributes.length || !selectedAttributesStatus) {
setSelectedAttributes([...selectedAttributes, value]);
}
setShowOptions([...showOption, value]);
getCategoricalVar(extractorId, nameAttribute)
.then((resp) => {
const newAttributeValue = {
index: event,
value: resp,
};
fields.get(event).checkBoxOption = {};
setSelectedIndex(false);
console.log('fields.get(event).checkBoxOption: ', fields.get(event).checkBoxOption);
let attributeValuesStatus = false;
for (let i = 0; i < attributeValues.length; i += 1) {
if (newAttributeValue.index === attributeValues[i].index) {
attributeValues[i].value = newAttributeValue.value;
attributeValuesStatus = true;
}
}
if (!attributeValues.length || !attributeValuesStatus) {
setAttributeValues([...attributeValues, newAttributeValue]);
}
})
.catch(printError);
};
Function which is setting the value on checkbox
const getCheckboxList = (index) => {
for (let i = 0; i < attributeValues.length; i += 1) {
if (attributeValues[i].index === index) {
return attributeValues[i].value;
}
}
return [];
};

Resources