Home > Software design >  React useRef useImperativeHandle
React useRef useImperativeHandle

Time:03-21

In the login form bellow, I use, in my submit function, document.getElementById(). It works but I heard today it's not a grood pratice and I better use useRef() instead.

But after some research, useRef() can't work with custom components, like my <FormInput/>. After some re-research, I heard about forwardRef() and useImperativeHandle()

It still works, but I'm perplexed, my code seems to be more complex and less manageable.

What's your opinion about this use case?

Thanks in advance

INITIAL CODE

function Login() {
  const navigate = useNavigate()
  const dispatch = useDispatch()     

  async function handleSubmit(e) {
    e.preventDefault()
    await dispatch(
      fetchOrUpdateLogin({
        email: document.getElementById('username').value,
        password: document.getElementById('password').value,
      })
    )
    navigate('/user')
  }

  return (
    <main className="main bg-dark">
      <section className="sign-in-content">
        <i className="fa fa-user-circle sign-in-icon"></i>
        <h1>Sign In</h1>
        <form onSubmit={handleSubmit}>
          <FormInput
            content="Username"
            type="text"
            idFor="username"
            prefill={USER.email}
          />
          <FormInput
            content="Password"
            type="password"
            idFor="password"
            prefill={USER.password}
          />
          <div className="input-remember">
            <input type="checkbox" id="remember-me" />
            <label htmlFor="remember-me">Remember me</label>
          </div>
          <Button type="submit" content="Sign In" classStyle="sign-in-button" />
        </form>
      </section>
    </main>
  )
}

function FormInput({ content, type, idFor, prefill }) {
  return (
    <div className="input-wrapper">
      <label htmlFor={idFor}>{content}</label>
      <input type={type} id={idFor} autoComplete="off" defaultValue={prefill} />
    </div>
  )
}

FINAL CODE

function Login() {
  const navigate = useNavigate()
  const dispatch = useDispatch()

  const emailValue = useRef(null)
  const passwordValue = useRef(null)

  async function handleSubmit(e) {
    e.preventDefault()
    await dispatch(
      fetchOrUpdateLogin({
        email: emailValue.current.inputValue(),
        password: passwordValue.current.inputValue(),
      })
    )
    navigate('/user')
  }

  return (
    <main className="main bg-dark">
      <section className="sign-in-content">
        <i className="fa fa-user-circle sign-in-icon"></i>
        <h1>Sign In</h1>
        <form onSubmit={handleSubmit}>
          <FormInput
            content="Username"
            type="text"
            idFor="username"
            prefill={USER.email}
            ref={emailValue}
          />
          <FormInput
            content="Password"
            type="password"
            idFor="password"
            prefill={USER.password}
            ref={passwordValue}
          />
          <div className="input-remember">
            <input type="checkbox" id="remember-me" />
            <label htmlFor="remember-me">Remember me</label>
          </div>
          <Button type="submit" content="Sign In" classStyle="sign-in-button" />
        </form>
      </section>
    </main>
  )
}
const FormInput = ({ content, type, idFor, prefill }, ref) => {
  const valueRef = useRef(null)

  useImperativeHandle(ref, () => ({
    inputValue() {
      return valueRef.current.value
    },
  }))
  return (
    <div className="input-wrapper">
      <label htmlFor={idFor}>{content}</label>
      <input
        ref={valueRef}
        type={type}
        id={idFor}
        autoComplete="off"
        defaultValue={prefill}
      />
    </div>
  )
}

export default forwardRef(FormInput)

CodePudding user response:

As Cemil mentions, using state would be the more common solution to this. But if you need to use refs for something like this, you only need forwardRef, not useImperativeHandle:

const FormInput = ({ content, type, idFor, prefill }, ref) => {
  return (
    <div className="input-wrapper">
      <label htmlFor={idFor}>{content}</label>
      <input
        ref={ref}
        type={type}
        id={idFor}
        autoComplete="off"
        defaultValue={prefill}
      />
    </div>
  )
}

export default forwardRef(FormInput)

// in Login:
fetchOrUpdateLogin({
  email: emailValue.current.value,
  password: passwordValue.current.value
})

CodePudding user response:

I just wanted to ask this. Why don't you try a state based approach rather than using refs? I think it would be a better practice. That's how you can do.

  1. Create a state variable inside of component like

    const [value, setValue] = useState();

  2. Give your value and setValue function to your input as

    <input {...rest} value={value} onChange={e => setValue(e.target.value)} />

  3. Whenever the value changes, pass it to your parent component so that your component will run itself as a modular component and you will give just one job to your input component. You can do like this. Pass a prop to your input component like onChange which is a function:

    useEffect(() => { props.onChange(value)}, [value])

  4. Save them in your parent component's state.

  5. Use it from the state variables instead of taking the values from the ref.

I wrote it like this because you're using React and you should also use your components based on their re-rendering. It would be a better use-case

  • Related