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
Related
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.
I'm using materiel UI form component in a modal.
This modal can be opened for add or edit an item, so values can be empty or not.
I put default props values in a state but this is always empty and never get previous values...
Here is my code :
const Comp = (props) => {
const { edit, values } = props // edit props for editing user
// values is :
{
prenom: 'name',
nom: 'name'
}
// ...
const [nom, setNom] = React.useState(edit ? values.nom : '')
const [prenom, setPrenom] = React.useState(edit ? values.prenom : '')
// ...
return (
<form>
<TextField
id="prenom"
value={prenom}
label="Prénom"
variant="outlined"
onChange={(event) => setPrenom(event.target.value)}
/>
<TextField
id="nom"
value={nom}
label="Nom"
variant="outlined"
onChange={(event) => setNom(event.target.value)}
/>
</form>
)
}
Thanks for your help
I'm guessing that you have your Comp used on the parent but not visible till some state changes, something like isDialogOpen. Then once the user wants to edit some object you do something like
setIsDialogOpen(true);
setDialogEditMode(true);
setValuesToEdit({nom: 'Foo', prenom: 'Bar'});
You have to understand that once you use the component (<Comp prop='value' />) React renders it, even that nothing gets to the actual Dom, the Comp function will be called! so at first it's being called with some empty values, then you want to let the user do the editing, you change the parent state. BUT the Comp state is still the state that it was created with (Empty values).
Remember this: useState acts in two ways:
On the first render it returns the value from the given parameter.
Any future renders it ignores the parameter and returns the saved state.
So in order to change the saved state you have to declare a reaction/effect when the props change, you do that with useEffect inside your Comp
for example:
useEffect(() => {
setNom(values.nom);
setPrenom(values.prenom);
}, [values])
I am unable to figure out how I could get pre-populated value from disabled input field. Since the field is disabled onChange won't apply here. Even with inputRef property of TextField, I am able to trigger a function called getNameInputValue and can see the values via console. But when I try to set the state to a function coming via props, I get null errors for value.
Here's the relevant code snippet:
const getNameInputValue = (id, nameValue) => {
console.log(id, nameValue.value); //works
props.getNameValues({ ...nameValues, [id]: nameValue.value }) //doesnt work
}
return (
<Box>
<Typography>NAME</Typography>
<TextField
id={`name-${index}`}
disabled
defaultValue={nameValueComingFromTheLoop}
variant="outlined"
inputRef={(inputValue) => getNameInputValue(`name-${index}`,inputValue)}
/>
</Box>
);
As requested getNameValues is defined in an outer component, here is its definition:
const [nameValue, setNameValue] = useState({});
const getNameValues= (receivedNameValuesObj) => {
setNameValue(receivedNameValuesObj);
};
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
I have a dropdown menu that is used to select a State. It is used to display different data per state. I am console.log(), Whenever I go to select a state and the initial one is '' and when I change again, it seems to be one step behind. For example: My dropdown menu starts empty, I then select New York. It prints '', I then select Maine, It prints New York. I think I understand why it is happening but I cannot figure out how to fix it.
ProjectStatus.js
const [selectedState, setSelectedState] = useState('');
const handleChange = event => {
setSelectedState(event.target.value);
console.log(selectedState);
};
return (
<div className='project-status-nav'>
<h3>Project Status Page</h3>
<hr className='separator' />
<FormControl variant='filled' className={classes.formControl}>
<InputLabel htmlFor='selectState'>Select State</InputLabel>
<Select
id='selectState'
className='selectStyle'
value={selectedState}
onChange={handleChange}>
<MenuItem value={''}> </MenuItem>
<MenuItem value={'New York'}>New York</MenuItem>
<MenuItem value={'New Jersey'}>New Jersey</MenuItem>
<MenuItem value={'Colorado'}>Colorado</MenuItem>
<MenuItem value={'Florida'}>Florida</MenuItem>
<MenuItem value={'Maine'}>Maine</MenuItem>
</Select>
</FormControl>
</div>
);
That happens because state isn't immediatly updated. When the function is called, selectedState is declared and doesn't change untill the next call (update).
You can if you want observe selectedState changes with useEffect, or create an custom hook to do it like this:
// works like the React class setState, but the callback is called passing the new State
function useStateCallback(initialState) {
const [[state, cb], setState] = useState([initialState, null]);
useEffect(
() => {
cb && cb(state);
},
[state]
);
const setStateCallback = (value, callback) => setState([value, callback]);
return [state, setStateCallback];
}
function App() {
const [val, setVal] = useStateCallback(0);
const increment = () =>
setVal(val + 1, newVal => alert("Incremented: " + newVal));
}
setSelectedState is not synchronous, so when you log the value of selectedState just after calling setSelectedState, you get the current value, not the new one. That's why you're always one step behind
I faced the same problem and solved it like this.
const [breakwater, setBreakwater] = useState(props.breakwater);
useEffect( () => {
props.handleBreakwater(breakwater); //I updated the parent component's state in useEffect
},[breakwater]);
const handleprimaryBwType = (e) =>{
setBreakwater({...breakwater, primaryBwType : e.target.value});
//props.handleBreakwater(breakwater); when i updated here a selection was updating from behind
}