Handle an input with React hooks
Asked Answered
L

7

157

I found that there are several ways to handle user's text input with hooks. What is more preferable or proper way to handle an input with hooks? Which would you use?

1) The simplest hook to handle input, but more fields you have, more repetitive code you have to write.

const [username, setUsername] = useState('');
const [password, setPassword] = useState('');

events:

onChange={event => setPassword(event.target.value)}
onChange={event => setUsername(event.target.value)}

2) Similar to above example, but with dynamic key name

const [inputValues, setInputValues] = useState({
  username: '', password: ''
});

const handleOnChange = event => {
  const { name, value } = event.target;
  setInputValues({ ...inputValues, [name]: value });
};

event:

onChange={handleOnChange}

3) An alternative to useState, and as said on ReactJS docs, useReducer is usually preferable to useState.

const [inputValues, setInputValues] = useReducer(
  (state, newState) => ({ ...state, ...newState }),
  {username: '', password: ''}
);

const handleOnChange = event => {
  const { name, value } = event.target;
  setInputValues({ [name]: value });
};

event:

onChange={handleOnChange}

4) useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.

const [inputValues, setInputValues] = useState({ 
  username: '', password: '' 
});

const handleOnChange = useCallback(event => {
  const { name, value } = event.target;
  setInputValues({ ...inputValues, [name]: value });
});

event:

onChange={handleOnChange}
Leonidaleonidas answered 19/4, 2019 at 6:52 Comment(1)
useCallback doesn't make much sense without the 2nd argument (array of dependencies)... IMHO useReduce is more flexible and less error prone than useState for objectsQuoits
K
143

How about writing a reusable function that returns the input value ... and the <input> itself:

 function useInput({ type /*...*/ }) {
   const [value, setValue] = useState("");
   const input = <input value={value} onChange={e => setValue(e.target.value)} type={type} />;
   return [value, input];
 }

That can then be used as:

 const [username, userInput] = useInput({ type: "text" });
 const [password, passwordInput] = useInput({ type: "text" });

 return <>
   {userInput} -> {username} <br />
   {passwordInput} -> {password}
 </>;
Kurgan answered 22/4, 2019 at 22:25 Comment(10)
could you please throw more light on the syntax after the return. It seems pretty new to me.Peafowl
@JonathanAkweteyOkine if you are talking about the second return, that's the shorthand syntax for React Fragments. See also here: reactjs.org/docs/fragments.htmlVerret
@Verret thanks. But the syntax i was referring to is this {userInput} -> {username}Peafowl
reactjs.org/docs/…Kurgan
Is that necessary to use prefix use in functions that are not using any hooks?Ironsides
@NiyasNazar no it is not necessary. Actually it is missleading.Kurgan
@Verret The -> arrow is just being rendered as text, and is not any special syntax.Jenicejeniece
@JonasWilms won't this rebind the onChange listener each evocation?Orthochromatic
@Orthochromatic yes, it would. This snippet is not "ready for copy & paste" it is meant to demonstrate how hooks could be used in the case given with a most minimal example. For sure you can add an useCallback to handle this.Kurgan
Please don't do this. HTML is a precious public good. Abstracting a fundamental web brick into a function like this, then misting the windows with useState and destructuring, will boggle your less imaginative colleagues, and cut them off from a world of HTML goodness. 100 votes for this answer makes me think I'm in the wrong group of penguins.Subroutine
L
68

This is how i'm using right now:

const [inputValue, setInputValue] = React.useState("");

const onChangeHandler = event => {
   setInputValue(event.target.value);
};

<input
   type="text"
   name="name"
   onChange={onChangeHandler}
   value={inputValue}
/>
Limitation answered 3/10, 2019 at 0:45 Comment(4)
Was going to comment this. This is the simplest way I feel. Also, this is how Dan Abramov demonstrated it when introducing hooks here: youtube.com/watch?v=dpw9EHDh2bM @ 25:25Sestos
You should wrap onChangeHandler with useCallback though.Surgical
why @AndreasLinnert?Limitation
@Limitation Okay, update on this: It seems it's not that simple. Just calling useCallback() costs more performance than it saves in this case. It's only useful in conjunction with child components that are wrapped in React.memo().Surgical
K
41

Yes you can handle react hooks with useState()

import React, {useState} from 'react'

export default () => {
    const [fName, setfName] = useState('');
    const [lName, setlName] = useState('');
    const [phone, setPhone] = useState('');
    const [email, setEmail] = useState('');

const submitValue = () => {
    const frmdetails = {
        'First Name' : fName,
        'Last Name' : lName,
        'Phone' : phone,
        'Email' : email
    }
    console.log(frmdetails);
}

return(
    <>
    <hr/>
    <input type="text" placeholder="First Name" onChange={e => setfName(e.target.value)} />
    <input type="text" placeholder="Last Name" onChange={e => setlName(e.target.value)} />
    <input type="text" placeholder="Phone" onChange={e => setPhone(e.target.value)} />
    <input type="text" placeholder="Email" onChange={e => setEmail(e.target.value)} />
    <button onClick={submitValue}>Submit</button>
    </>
    )
}
Karlise answered 4/8, 2019 at 1:20 Comment(0)
A
4

Here's how I do it (assuming your inputs must be inside a form):

I have a BasicForm component that I use.

It stores all the inputs state into an object into a single useState() call.

It passes via useContext() the inputs state along with an onChange() function and a function setInputInitialState() for the inputs to set their initial state when they are first mounted. It also passes onFocus, onBlur, and it has functions to validate fields which I'm not showing here to simplify the code.

This way I can easily create a form with as many inputs as I want, like:

<BasicForm
      isSubmitting={props.isSubmitting}
      submitAction={ (formState) =>
        props.doSignIn(formState) }
    >
      <TextInput
        type='email'
        label='Email'
        name='email'
        placeholder='Enter email...'
        required
      />
      <TextInput
        type='password'
        label='Password'
        name='password'
        placeholder='Enter password...'
        min={6}
        max={12}
        required
      />
      <SubmitButton
        label='Login'
      />
    </BasicForm>

BasicForm.js

import FormContext from './Parts/FormContext';

function BasicForm(props) {

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

  function onChange(event) {
    const newValue = event.target.value;
    const inputName = event.target.name;
    setInputs((prevState)=> {
      return({
        ...prevState,
        [inputName]: {
          ...prevState[inputName],
          value: newValue,
          dirty: true
        }
      });
    });
  }

  function setInputInitialState(
    inputName,
    label='This field ',
    type,
    initialValue = '',
    min = false,
    max = false,
    required = false) {

    const INITIAL_INPUT_STATE = {
      label: label,
      type: type,
      onFocus: false,
      touched: false,
      dirty: false,
      valid: false,
      invalid: false,
      invalidMsg: null,
      value: initialValue,
      min: min,
      max: max,
      required: required
    };

    setInputs((prevState) => {
      if (inputName in prevState) {
        return prevState;
      }
      return({
        ...prevState,
        [inputName]: INITIAL_INPUT_STATE
      });
    });

  }

return(
    <FormContext.Provider value={{
      onChange: onChange,
      inputs: inputs,
      setInputInitialState: setInputInitialState,
    }}>
      <form onSubmit={onSubmit} method='POST' noValidate>
        {props.children}
      </form>
    </FormContext.Provider>
  );
}

TextInput.js

The inputse use the useEffect() hook to set their initial state when they're mounted.

function TextInput(props) {

  const formContext = useContext(FormContext);

  useEffect(() => {
    console.log('TextInput useEffect...');
    formContext.setInputInitialState(
      props.name,
      props.label,
      props.type,
      props.initialValue,
      props.min,
      props.max,
      props.required
    );
  },[]);

  return(
      <input
        type={props.type}
        id={props.name}
        name={props.name}
        placeholder={props.placeholder}
        value={([props.name] in formContext.inputs) ?
                  formContext.inputs[props.name].value
                : props.initialValue || ''}
        onChange={formContext.onChange}
        onFocus={formContext.onFocus}
        onBlur={formContext.onBlur}
      >
      </input>
      </div>
      {([props.name] in formContext.inputs) ?
          formContext.inputs[props.name].invalidMsg && <div><span> {formContext.inputs[props.name].invalidMsg}</span></div>
        : null}
    </div>
  );

...
}
Aldwon answered 19/4, 2019 at 9:33 Comment(0)
M
3
function App(){
    const [name, setName] = useState("");
    const [istrue, Setistrue] = useState(false);
    const [lastname,setLastname]=useState("");
    
    function handleclick(){
       Setistrue(true);
    }
    
    return(
        <div>
            {istrue ? <div> <h1>{name} {lastname}</h1> </div> : 
            <div>
                <input type="text" placeholder="firstname" name="name" onChange={e =>setName(e.target.value)}/>
                <input type="text" placeholder="lastname" name="lastname" onChange={e =>setLastname(e.target.value)}/>
               <button  type="submit" onClick={handleclick}>submit</button>
            </div>}
        </div>
    )
    
    }

}
Menke answered 7/2, 2020 at 8:47 Comment(0)
I
1

You may want to consider a form library like Formik

Irreverence answered 28/6, 2020 at 13:43 Comment(2)
It is exactly what is mentioned in React Docs: reactjs.org/docs/forms.html#fully-fledged-solutionsHygienist
I was about to suggest the same.. why downvotes for this answer?Basically
D
0

Thanks your answers and explanations from above: I implement these using Flask and React Hook with three button (start search data from server, update ag-grid table and cell data, and Save data to server) with Ag-Grid. I put in the version 106. https://github.com/peterhchen/900_ReactJS_Flask_FullStack

Debra answered 8/3, 2023 at 21:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.