React Hooks: handle multiple inputs
Asked Answered
S

8

10

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?

Safelight answered 2/9, 2020 at 17:33 Comment(4)
You will discover that hooks are essentially a way to use instance properties/methods in functional components (where you don't have an instance - hint, react keeps one for you). So start by turning your component into a function which is just it's render method, and then sprinkle in a useStateDwarfism
so i made a custom hook as such: const useInputs =[inputs, setInputs] = useState(''); setInputs({[e.target.name]: e.target.value}) so if i call this in the onChange of a input (as shows in the class example) I cant cuz i cant call an hook there.Safelight
Put the state hook up top in the function and call the setInput method in the onChange handler. Also, your state should be an object whose keys are the field names and whose values are the field values, just like in your class component. One other thing, when using hooks, you can only “patch” state (give it a partial object) by using the function signature to set the state of the useState hookDwarfism
There is a simple react form package use-flat-form made for similar use case.Photocomposition
D
8

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

Dwarfism answered 2/9, 2020 at 18:56 Comment(1)
this answer is very nice but one thing that make it the best answer for this question and that tip is on the onChange method for shortern answer write onChangeForField and on them method onCHangeForField write const onChangeForField = ({ target }) => setInputs(prevInputs => ({ ...prevInputs, [target.name]: target.value . my tip is just remove fieldName from method and get the name from target instead of functionLowis
C
18

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} />
     </>
   )
}
Clearstory answered 23/4, 2021 at 17:9 Comment(1)
This is the best answer to the above problem, simple concise and clearSnooker
D
8

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

Dwarfism answered 2/9, 2020 at 18:56 Comment(1)
this answer is very nice but one thing that make it the best answer for this question and that tip is on the onChange method for shortern answer write onChangeForField and on them method onCHangeForField write const onChangeForField = ({ target }) => setInputs(prevInputs => ({ ...prevInputs, [target.name]: target.value . my tip is just remove fieldName from method and get the name from target instead of functionLowis
M
2

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)}

Mislead answered 2/9, 2020 at 19:9 Comment(0)
J
1

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}/>
     </>
Jecon answered 27/5, 2022 at 23:36 Comment(0)
P
0

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,
            };
        }),
    []
);
Photocomposition answered 22/3, 2021 at 14:39 Comment(0)
M
0

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}
/>
Methinks answered 2/11, 2021 at 15:53 Comment(0)
B
0

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));
Bottleneck answered 10/3, 2022 at 7:34 Comment(0)
C
0

I am facing same issue or have same question. I tried above solutions but wont work for me. So i tried following solution and worked for me

const [inputFields, setInputFields] = useState({
        inp1: "",
        inp2: ""
});

const handleChange = (e) => {
        // Destructure the name and value from the event target
        const { name, value } = e.target;
        setInputFields({ ...inputFields, [name]: value });
};

<input
        type="text"
        name="title" //i forget to add this
        value={inputFields.title}
        className="form-control"
        placeholder="A good title attract more users"
        onChange={handleChange}
/>

Note : My react version is 18.2.0

Cunha answered 12/3 at 9:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.