'React Hooks: handle multiple inputs

on react docs forms section there is the following example using class components:

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

Considering Hooks can only be called either in a React function component or a custom React Hook function is there a way of doing it using hooks instead?



Solution 1:[1]

example

const MyComponent = () => {
   const [inputs,setInputs] = useState({});

   return (
     <> 
      <input key="field1" name="field1" onChange={({target}) => setInputs(state => ({...state,field1:target.value}))} value={inputs.field1}/>
      <input key="field2" name="field2" onChange={({target}) => setInputs(state => ({...state,field2:target.value}))} value={inputs.field2}/>
     </>
   )

}

you can pass in initial values like this:

const MyComponent = (initialValues = {}) => {
   const [inputs,setInputs] = useState(initialValues);
   ...
}

EDIT: A nice short onChange according to @hamidreza's comment

const MyComponent = (initialValues = {}) => {
   const [inputs,setInputs] = useState(initialValues);
   const onChangeHandler = useCallback(
     ({target:{name,value}}) => setInputs(state => ({ ...state, [name]:value }), [])
   );

   return (
     <> 
      <input key="field1" name="field1" onChange={onChangeHandler} value={inputs.field1}/>
      <input key="field2" name="field2" onChange={onChangeHandler} value={inputs.field2}/>
     </>
   )


}

etc, etc, etc

Solution 2:[2]

you can clean up @adam 's final solution a bit by not using the useCallback hook, and instead simply using the useState hook as a controlled component.

const MyComponent = () => {
   const [inputs, setInputs] = useState({});
   const handleChange = e => setInputs(prevState => ({ ...prevState, [e.target.name]: e.target.value }));

   return (
     <> 
      <input name="field1" value={inputs.field1 || ''} onChange={handleChange} />
      <input name="field2" value={inputs.field2 || ''} onChange={handleChange} />
     </>
   )
}

Solution 3:[3]

Maybe, on the last example onChangeForField('...') will be triggered on each render, so maybe you have to write onChange={()=>onChangeForField('...')} or if you want the event to get passed onChange={(e)=>onChangeForField('...', e)}

Solution 4:[4]

I was looking for the same answer,but i was finding difficulty to understand the previous solutions,so i tried in my own way ,and i found a solution.

   const [inputs,setInputs] = useState({
     'field1':'',
     'field2':'',
    });
   const handleChange = (e) => {
     const name = e.target.name; //it is the name of that input
     const value = e.target.value; //value of that input
   
     setInputs((prev) => {
       prev[name] = value;//changing the updated value to the previous state
     
       return prev;
      });
     };

    
   return (
     <> 
      <input key="field1" name="field1" onChange={handleChange} value={inputs.field1}/>
      <input key="field2" name="field2" onChange={handleChange} value={inputs.field2}/>
     </>

Solution 5:[5]

adding to Adam's answer and for those who are looking towards typescript solution,

interface MyIType {
    field1: string;
    ...
}

//Partial from typescript to make properties optional
interface MyFormType extends Partial<MyIType> {}

const [inputs,setInputs] = useState<MyFormType>(initialValues);
    
const onChangeForField = useCallback(({target}) => 
    setInputs(_state => {
            return {
                ..._state,
                [target.name]: target.value,
            };
        }),
    []
);

Solution 6:[6]

If you were like me, having multiple inputs on multiple pages using the same input id/name/key, try value={data.xxx || ''} . Full code:

const [data, setData] = useState<any>({});

const handleValueChanges = e => { 
    setData({
      ...data,
      [e.target.name]: e.target.value,
    });
};

<InputText  (using prime react)
    id="firstName"
    name="firstName"
    value={data.firstName || ''}
    onChange={handleUpdate}
/>

Solution 7:[7]

As of v6 you can use .forEach(), Please refer to the migrate guide

[{name: "firstName", value: "Safwat" }, {name: "lastName", value: "Fathi", }].forEach(({name, value}) => setValue(name, value));

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ian Rios
Solution 2 Ian Rios
Solution 3 JB_DELR
Solution 4 Md Rakibul Islam
Solution 5 9paradox
Solution 6 Dharman
Solution 7 Safwat Fathi