Handle Custom Input with useImperativeHandle

Understanding useImperativeHandle with example


React chooses rendering UI based on props and state, it is also called declarative UI. There is another approach, imperative, that is useful when your program requires more fine-grow control over data flow. In this article, I'll explain how we can use the Imperative method to deal with data flow.

In React, you define steps to manipulate DOM to update node elements. React provides refs to directly access the DOM, but refs expose the element node or component itself. React introduces useImperativeHandle hook, which exposes its own specific functions and native properties outside of the child component. 

useImperativeHandle 

You can call the useImperativeHandle hook on the child component to define properties and methods that should be accessible from a parent component. 

It takes two arguments, ref which you get from forwardRef, and handler function, which returns mostly objects with a group of all properties or methods you want to expose. You can also use dependencies array but it's optional.


useImperativeHandle(ref, () => {
  return { } 
}) 

The useImperativeHandle only works in combination with forwardRef.

Form validation 

Let's say you have a child component called Input that contains a validation function. You want to allow the parent component to imperatively check validation on email. Here is how you can use useImperativeHandle in functional component.

import {
   forwardRef,
   useRef, 
   useState, 
   useImperativeHandle } from 'react'

const Input = forwardRef(({label, id, ...props}, ref) => {
  const emailRef = useRef(null)

  const [error, setError] = useState(null)

  const validateInput = () => {
    const enteredEmail = emailRef.current.value
     if (enteredEmail === '' && !enteredEmail.includes('@')) {
      setError('Please Enter valid email address')
      } else {
        setError(null)
      }
    }
    // expose validateInput function to parent component
    useImperativeHandle(ref, () => {
      return {
        validate: validateInput
      }
    })

    return (
      <div>
        <label htmlFor={id}>{label}</label>
        <input id={id} { ...props } ref={emailRef} />
        { error && <p style={{color: 'beige', textShadow: '0 0 2px #ccc'}}>{error}</p> }
      </div>
    )
  })

export default Input


The validateInput function shows an error if the input field is empty or email address doesn't contain @ symbol. 

The useImperativeHandle allows the Input component to expose the validate function to its parent component, which may then be used to validate the form before submission. The ref objects allow the parent component to interact directly with the child component.

import classes from './SubscriptionForm.module.css'
import Input from "./Input"

import { useRef } from "react"

const SubscriptionForm = () => {
  const inputRef = useRef(null)
  
  function handleSubmitForm(event) {
    event.preventDefault()

    if (inputRef.current) {
      inputRef.current.validate()
    }
  }
 
  return (
    <form onSubmit={handleSubmitForm}>  
      <Input 
        label="Email" 
        type="email"
        id="email"
        ref={inputRef}
         />
      <button>Subscribe</button>
    </form>
  )
}

export default SubscriptionForm

Here we used useRef hook to create a reference to the Input component. The parent component called `inputRef.current.validate()` function imperatively to interact with the input. The parent maintains control over validating the form but doesn't need to manage the underlying <input> DOM node.

Conclusion

When the useImperative hook is combined with forwardRef, the ref object exposes a custom value rather than forwarding to input.

You will use useImperativeHandle rarely but in some cases, this hook can be very helpful like focusing a node, exposing custom value, level of control, direct interaction, or third-party integration.


0 Comments:

Post a Comment