Cannot set state of multiple checkboxes? - reactjs

I am rendering multiple checkboxes:
className, content, visible, handleFeaturesChange}= props;
...
<FormCheck type="checkbox" label={"parking"} name={"parking"} onChange={handleFeaturesChange}></FormCheck>
<FormCheck type="checkbox" label={"Laundry"} name={"laundry"} onChange={handleFeaturesChange}></FormCheck>
from parent component:
function handleFeaturesChange(event) {
// event.preventDefault();
const {name, checked} = event.target
setFeatures({
...features,
[name]: checked
})
}
But this results in state being over-riden onChange so that only one checkbox is recorded as checked in any instance.
How do I get multiple checkboxes to work together?

It looks like you're not binding the state value to each checkbox. It looks like this can be fixed by binding the values:
<FormCheck
type="checkbox"
label={"parking"}
name={"parking"}
onChange={handleFeaturesChange}
checked={features.parking}
/>
Note: This assumes the FormCheck component takes a checked prop. If not, you'll need to add that to FormCheck.

you have store the checkBox values in a global variable then setState on that variable,otherwise you are just setting state for current checked box value
Note:this is not correct code,this is only for your understanding and I am assuming setFeatures as a function where You setState()
let globalCheckBox=[]
<FormCheck
type="checkbox"
label={"parking"}
name={"parking"}
onChange={handleFeaturesChange}
checked={features.parking}
/>
function handleFeaturesChange(event) {
const {name, checked} = event.target
globalCheckBox.push({name, checked})
setFeatures({
...features,
globalCheckBox
})
}
`

You can use the state hook, useState.
const [checkedItems, setCheckedItems] = React.useState([false, false])
Then:
<Checkbox
isChecked={checkedItems.parking}
onChange={e => setCheckedItems([e.target.checked, checkedItems.parking])}
/>
https://reactjs.org/docs/hooks-state.html

Related

Unchecking a checkbox in react from several checkbox groups (I'm using React hooks)

I have several checkboxes running in several checkbox groups. I can't figure out how to uncheck (thus changing the state) on a particular checkbox. FOr some reason I can't reach e.target.checked.
<Checkbox
size="small"
name={item}
value={item}
checked={checkboxvalues.includes(item)}
onChange={(e) => handleOnChange(e)}
/>
and my function
const handleOnChange = (e) => {
const check = e.target.checked;
setChecked(!check);
};
I made a working sample of the component in this sandbox.
You need to create handleOnChange function specific to each group. I have created one for Genre checkbox group in similar way you can create for other groups.
Here is handler function.
const handleOnChangeGenre = (e) => {
let newValArr = [];
if (e.target.checked) {
newValArr = [...state.pillarGenre.split(","), e.target.value];
} else {
newValArr = state.pillarGenre
.split(",")
.filter((theGenre) => theGenre.trim() !== e.target.value);
}
setState({ ...state, pillarGenre: newValArr.join(",") });
};
pass this function as handleOnChange prop to CustomCheckboxGroup as below.
<CustomCheckboxGroup
checkboxdata={genres}
checkboxvalues={state.pillarGenre}
value={state.pillarGenre}
sectionlabel="Genre"
onToggleChange={handleGenreSwitch}
togglechecked={genreswitch}
handleOnChange={handleOnChangeGenre}
/>
comment your handleOnChange function for testing.
check complete working solution here in sandbox -
complete code
Here's how I'd do it: https://codesandbox.io/s/elastic-pateu-flwqvp?file=/components/Selectors.js
I've abstracted the selection logic into a useSelection() custom hook, which means current selection is to be found in store[key].selected, where key can be any of selectors's keys.
items, selected, setSelected and sectionLabel from each useSelection() call are stored into store[key] and spread onto a <CustomCheckboxGroup /> component.
The relevant bit is the handleCheck function inside that component, which sets the new selection based on the previous selection's value: if the current item is contained in the previous selected value, it gets removed. Otherwise, it gets added.
A more verbose explanation (the why)
Looking closer at your code, it appears you're confused about how the checkbox components function in React.
The checked property of the input is controlled by a state boolean. Generic example:
const Checkbox = ({ label }) => {
const [checked, setChecked] = useState(false)
return (
<label>
<input
type="checkbox"
checked={checked}
onChange={() => setChecked(!checked)}
/>
<span>{label}</span>
</label>
)
}
On every render, the checked value of the <input /> is set according to current value of checked state. When the input's checked changes (on user interaction) the state doesn't update automatically. But the onChange event is triggered and we use it to update the state to the negative value of the state's previous value.
When dealing with a <CheckboxList /> component, we can't serve a single boolean to control all checkboxes, we need one boolean for each of the checkboxes being rendered. So we create a selected array and set the checked value of each <input /> to the value of selected.includes(item) (which returns a boolean).
For this to work, we need to update the value of selected array in every onChange event. We check if the item is contained in the previous version of selected. If it's there, we filter it out. If not, we add it:
const CheckboxList = ({ items }) => {
const [selected, setSelected] = useState([])
const onChecked = (item) =>
setSelected((prev) =>
prev.includes(item)
? prev.filter((val) => val !== item)
: [...prev, item]
)
return items.map((item) => (
<label key={item}>
<input
type="checkbox"
checked={selected.includes(item)}
onChange={() => onChecked(item)}
/>
<span>{item}</span>
</label>
))
}
Hope that clears things up a bit.
The best way to do it, it's to save selected checkboxes into a state array, so to check or uncheck it you just filter this state array based on checkbox value property that need to be unique.
Try to use array.some() on checkbox property checked. To remove it it's just filter the checkboxes setted up in the state array that are different from that single checkbox value.

suppress warning "Warning: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field." in React

I have a warning that says
Warning: You provided a `value` prop to a form field without an `onChange` handler.
This will render a read-only field. If the field should be mutable use `defaultValue`.
`Otherwise, set either `onChange` or `readOnly`.`
Of course, I have googled a solution for it. One way to fix it is to replace "value" attribute with "defaultValues". But, this is causing another issue because my component is a controlled.
I could add onchange attribute and method to the fields. But the problem is that it will cause redundancy as I have a single onChange method on the tag that manages all the values and states. For example,
const [allStates, setAllStates] = useState({value_one: "", value_two:""});
function handleOnChange(event){
.........
//update the allStates state here
}
<Form onChange={handleOnChange}>
<input value={allStates.value_one} />
<input value={allStates.value_two} />
</Form>
What are the alternatives I have? Should I just ignore it since I can only see it on dev environments, the end users cannot and won't see it on the prod environment anyway?
It is a Helpful Warning by React, you can probably refer to this thread which discusses this.
https://github.com/facebook/react/issues/1118
Although adding the defaultValue should work for you, since your state has the latest value for the inputs, and when your component would render for the first time as long as the defaultValue is assigned to your <input>, the fields would reflect your state.
const [state, setState] = useState({});
const handleChange = (ev) => {
setState(prev_state => {
return {
...prev_state,
[ev.target.name]: ev.target.value
}
})
}
<form onChange={handleChange}>
<input name={"foo"} defaultValue={state.foo}/>
<input name={"bar"} defaultValue={state.bar}/>
</form>
The warning it is giving you is that you have provided the input with a value argument, so when you go to type in the input, it won't work:
const [allStates, setAllStates] = useState({
value_one: 'I will be display in the input initially', // <-- This text will already be entered in the text input.
value_two: ''
});
<input value={allStates.value_one} />
But since you added the value prop, you need to give it an onChange, otherwise the value of the field will always be the same and you won't be able to change it.
Ofcourse you can add the onChange like this:
const [allStates, setAllStates] = useState({
value_one: '',
value_two: ''
});
const handleOnChange = (event) => {
setAllStates({
...allStates,
// Now you can target the name prop
[event.target.name]: event.target.value
});
}
return (
<>
{/* I will suggest adding a name prop also */}
<input name="value_one" value={allStates.value_one} onChange={handleOnChange} />
<input name="value_two" value={allStates.value_two} onChange={handleOnChange} />
</>
);
The name prop is really useful for these one function setups, as it allows you to target the state value straight from the name.
But remember that the name values should be the same as the values in the state.

React Hooks SetState Method isn't updating the state at all

I am using a Material UI Select Component to render a simple drop down menu, with its value as a state declares using the useState method.
const [collaboratingTeams, setCollaboratingTeams] = useState([])
The below code is of the Select Component, with its value and the corresponsing handler function in its onChange prop.
<Select
validators={["required"]}
errorMessages={["this field is required"]}
select
multiple
variant="outlined"
value={collaboratingTeams}
name="collaboratingTeams"
onChange={(e) => handleSelectCollaboratingTeams(e)}
helperText="Select Collaborating Teams "
>
{arrTeams.map((option, index) => (
<MenuItem
key={option.teamId}
value={option.teamId}
variant="outlined"
>
<Checkbox
checked={collaboratingTeams.indexOf(option.teamId) !== -1}
/>
<ListItemText primary={option.teamValue} />
</MenuItem>
))}
</Select>
The below code is the function that triggers when a drop down data is changed.
This function sets the state, which should then technically update the Select's selected options.
const handleSelectCollaboratingTeams =(e)=>{
setCollaboratingTeams(e.target.value)
}
The issue is, the setCollaboratingTeams method isn't updating the state only. I understand that the setstate method in hooks works so coz of its asynchronous nature but at some point it should display up right. Don't understand where I'm going wrong.
I expect the collaboratingTeams array to be updated with a new value when a new value is selected by the user.
you should define the new state for storing the selected item.
Example for class component:
state = {
selectedOption: null,
};
handleChange = selectedOption => {
this.setState({ selectedOption });
};
Example for functional component(using React-hook):
const [selectedOption, setSelectedOption] = useState(null);
handleChange = selectedOption => {
setSelectedOption(selectedOption);
};
dont use arrow function with onchange it often used when we need to pass id or some other data

react set value to another component when change value in different component

i have two react input components, where in one input component; value changes, i have to set the same value in another input field.
here is my two input components.
<div className="col-md-4">
<label htmlFor="saleamount">Payment </label>
<NumberFormat id="manualPaymentEntry" name="manualPaymentEntry" fixedDecimalScale={true} decimalScale={2} value={manualPayEntry.current} className="form-control" thousandSeparator={true} thousandsGroupStyle="lakh" onChange={(values) => {
const { formattedValue, value } = values;
handleManualPaymentEntry(value);
}} />
</div>
<div className="col-md-4">
<label htmlFor="selectedPayment">Actual Payment</label>
<NumberFormat readOnly={true} fixedDecimalScale={true} decimalScale={2} value={cumulativePay} className="form-control" thousandSeparator={true} thousandsGroupStyle="lakh" />
</div>
</div>
when there is change in manualPaymentEntry i have to set same value to cumulativePay
const [cumulativePay, setCumulativePay] = useState(0);
const manualPayEntry = useRef(0);
useEffect(() => {
setCumulativePay(manualPayEntry.current);
}, [manualPayEntry]);
const handleManualPaymentEntry = (value) => {
let val = parseFloat(value);
manualPayEntry.current = value;
}
i have used useRef for manualPayEntry to set current value to cumulativePay, but when there is change in manualPayEntry it is not setting value to cumulativePay, always i am getting '0' only..., but current value of manualPayEntry shouled get refelcted in cumulativePay
issue got resolved, after chaning onChange to onValueChange, as said in the documentation onchange will no longer get value , instead we need to use onValueChange
react-number-format npm
onValueChange is not same as onChange. It gets called on whenever
there is change in value which can be caused by any event like change
or blur event or by a prop change. It no longer receives event object
as second parameter.
Changing a value manually like you are doing it there:
manualPayEntry.current = value;
Does not trigger the rerendering functionalities of React. Why don't you just add the setCumulativePay directly in the handleManualPaymentEntry?
const handleManualPaymentEntry = (value) => {
let val = parseFloat(value);
manualPayEntry.current = value;
setCumulativePay(value);
}
Or you should treat manualPayEntry as a state as well and not a ref, though it's not really advised to change a state in a useEffect based on another state. I really think changing directly the needed state in your handler function is the best solution.

Do not mutate state directly, Use setState() react/no-direct-mutation-state in React JS

<input
defaultValue={this.props.str.name}
ref={(input) => { this.state.name = input; }}
name="name"
type="text"
className="form-control"
onChange={this.handleInputChange}
/>
handleInputChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
if(this.state.name.value === "") {
this.msg.show('Required fields can not be empty', {
time: 2000,
type: 'info',
icon: <img src="img/avatars/info.png" role="presentation"/>
});
}
I'm trying to set the default value like that and wanted to access it as well. I did like this and accessed the value with this.state.name.value but the thing is its working but showing the warning as
Do not mutate state directly, Use setState()
react/no-direct-mutation-state .
Getting "Do not mutate state directly, Use setState()", Why?
Because, you are mutating the state value inside ref callback method to store the node ref, Here:
this.state.name = input;
Solution:
Don't use state variable to store the reference, You can directly store
them in component instance because that will not change with time.
As per DOC:
The state contains data specific to this component that may change
over time. The state is user-defined, and it should be a plain
JavaScript object.
If you don’t use it in render(), it shouldn’t be in the state. For
example, you can put timer IDs directly on the instance.
Since you are using controlled input element, ref is not required. Directly use this.state.name with input element value property and this.state.name to access the value.
Use this:
<input
value={this.state.name || ''}
name="name"
type="text"
className="form-control"
onChange={this.handleInputChange}
/>
If you wanted to use ref then store the ref directly on instance, remove value property and you can remove the onChange event also, Use it like this:
<input
ref={el => this.el = el}
defaultValue={this.props.str.name}
name="name"
type="text"
className="form-control"
/>
Now use this ref to access the value like this:
this.el.value
you can instead clone the entire property value inside the with spread operator and then reform or edit the value for example :
state = {Counters: [{id:1,value:1},{id: 2,value: 2},{id: 3,value: 3},{id: 4,value: 4}]}
increment = (Counter) => {
//This is where the state property value is cloned
const Counters = [...this.state.Counters];
console.log(Counters);
const index = Counters.indexOf(Counter)
Counters[index].value++
this.setState({
Counters: this.state.Counters
})
}
Change your line number 3 as
ref={(input) => { this.setState({name: input}); }}

Resources