Reputation: 117
I have several states and I would like to write only 1 handler for all of them. On change, only 1 handler will trigger and change the state only if it has changed.
I have the solution for class based components like this
handleChange (evt) {
this.setState({ [evt.target.name]: evt.target.value });
}
But when I use function based components, I couldn't figure it out how.
export default function DateBar(props)
{
let date = new Date();
let i = 1;
const days = Array.from({length: new Date(date.getYear(), date.getMonth() + 1, 0).getDate()}).map(() => <option key = {uuidv4()} value={i}>{i++}</option>);
const monthOpts = props.months.map(month => <option key = {uuidv4()} value={month} name = {month} id = {month}>{month}</option>);
const yearsOpts = props.years.map(year => <option key = {uuidv4()} value ={year} name = {year} id = {year}>{year}</option>);
// states
const [today, setToday] = useState("");
const [thisMonth, setThisMonth] = useState("");
const [year, setYear] = useState("");
useEffect(() =>
{
setToday(date.getDate());
setThisMonth(props.months[date.getMonth()]);
setYear(date.getYear());
}, []);
const handleChange = () =>
{
// for example, if day is changed, i want to call setToday() somehow..
}
return(
<div className="DateBarRoot">
<FormControl className="formControl input" variant="outlined">
<InputLabel htmlFor="today">Day</InputLabel>
<Select
native
value={today}
onChange={handleChange}
label = 'day'
inputProps={{
name: 'today',
id: 'today',
}}
>
{days}
</Select>
</FormControl>
<FormControl className="formControl input" variant="outlined">
<InputLabel htmlFor="month">Month</InputLabel>
<Select
native
value={thisMonth}
onChange={handleChange}
label = 'month'
inputProps={{
name: 'thisMonth',
id: 'thisMonth',
}}
>
{monthOpts}
</Select>
</FormControl>
<FormControl variant="outlined" className="formControl input">
<InputLabel htmlFor="year">Year</InputLabel>
<Select
native
className="select"
value={year}
onChange={handleChange}
label = 'year'
inputProps={{
name: 'year',
id: 'year',
}}
>
{yearsOpts}
</Select>
</FormControl>
</div>
)
}
Thanks a lot in advance.
Upvotes: 1
Views: 3096
Reputation: 1073998
You can have a single state item that holds these values. In fact, this is covered by the useState
documentation:
Note
Unlike the
setState
method found in class components,useState
does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:const [state, setState] = useState({}); setState(prevState => { // Object.assign would also work return {...prevState, ...updatedValues}; });
Another option is
useReducer
, which is more suited for managing state objects that contain multiple sub-values.
In your case, it might be:
const [values, setValues] = useState({}); // Or maybe a Map
// ...
const handleChange = (event) => {
setValues(values => {
return {...values, [event.target.name]: event.target.value};
});
};
Or with some destructuring:
const [values, setValues] = useState({}); // Or maybe a Map
// ...
const handleChange = ({target: {name, value}}) => {
setValues(values => {
return {...values, [name]: value};
});
};
Or, as they say above, you can use useReducer
and, probably, a switch
on the event target name.
Upvotes: 5