Wokers
Wokers

Reputation: 117

how to handle multiple input changes with only 1 handler with hooks

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

Answers (1)

T.J. Crowder
T.J. Crowder

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

Related Questions