What's a better way to handle input changes in react?

Viewed 36

Everytime i want to update some value in my state via a text input i just simply create an onChange function that accepts a event: React.ChangeEvent<HTMLInputElement | HtmlTextAreaElement> as an argument. Something like this:

const [state, setState] = useState<IState>({ email: "", password: "" });

const onChange = (event: React.ChangeEvent<HTMLInputElement | HtmlTextAreaElement>): void => {
  const { name, value } = event.target;
  
  setState({
    ...state,
    [name]: string
  });
} 

And well it perfectly works but... i have to create this function every single time that i want to change my state via an input in other page (an onChange function in the login page, an onChange function in sign up page, forgotpassword page... etc). My question is: Is there a way to create a generic onChange that accepts any state and update it?

this is my first post on stack overflow, sorry if i dont follow the guidelines at all.

1 Answers

If the function is short and only used once, then one option you could consider would be to define it inline.

<input
  name="name"
  value={state.name}
  onChange={e => { setState({ ...state, name: e.target.value }); }}
/>

You can also consider separating out states, as React recommends - there's no need to put everything into a single state variable (unless there are a lot of inputs in a form, in which case a single state object with a generic updater can make things more DRY).

const [name, setName] = useState(''); // No need for type annotations anymore
const [password, setPassword] = useState('');

// ...

<input
  name="name"
  value={name}
  onChange={e => { setName(e.target.value); }}
/>

If you want a generic state updater that you don't have to redefine every time to have another state variable, you can create a higher order function that you pass in a state setter, that returns the change handler - then import this function wherever it's needed.

const makeChangeHandler = (stateSetter: React.Dispatch<React.SetStateAction<string>>) => {
  return (event: React.ChangeEvent<HTMLInputElement | HtmlTextAreaElement>) => {
    const { name, value } = event.target;
    stateSetter({
      ...state,
      [name]: value
    });
  };
};
<input
  name="name"
  value={name}
  onChange={makeChangeHandler(setState)}
/>

Refactoring that function to deal with separate states instead of having a single state object is trivial, if desired, just do

stateSetter(value);
Related