Context

Giả sử ta có đoạn code sau:

import { useState, useRef } from "react"
 
function App() {
  const [value, setValue] = useState("")
  const ref = useRef(null)
 
  return (
    <form>
      <input type="text" ref={ref} value={value} onChange={(e) => setValue(e.target.value)} />
      <button type="button" onClick={() => inputRef.current.focus()}>
        Focus
      </button>
    </form>
  )
}
 
export default App

Có thể thấy, ta sử dụng hook useRef để liên kết thẻ <input> với ref inputRef. Bằng cách này, ta có thể gọi những native method của thẻ <input> chẳng hạn như focus.

Tuy nhiên, nếu thay đổi thẻ <input> thành một component nào đó, phương thức focus sẽ không còn hoạt động được nữa:

import { useState, useRef } from "react"
import CustomInput from "./CustomInput"
 
function App() {
  const [value, setValue] = useState("")
  const inputRef = useRef(null)
 
  return (
    <form>
      <CustomInput
        type="text"
        ref={inputRef}
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <button type="button" onClick={() => inputRef.current.focus()}>
        Focus
      </button>
    </form>
  )
}

Khi đó, ta cần phải dùng phương thức forwardRef để chuyển ref từ component App sang cho component CustomInput như sau:

import React from "react"
 
function CustomInput(props, ref) {
  return <input ref={ref} {...props} />
}
 
export default React.forwardRef(CustomInput)

Trong đoạn code trên, ref sẽ nhận giá trị là thẻ <input> ở bên trong component CustomInput.

Usage

Trong trường hợp ta không muốn expose toàn bộ DOM node của thẻ <input> ở trong component CustomInput mà chỉ muốn expose ra những custom method để ref có thể sử dụng, ta có thể sử dụng hook useImperativeHandle.

Hook này nhận vào ba đối số là:

  1. Một ref
  2. Một callback trả về object có chứa những custom method
  3. Mảng các dependency

Ví dụ:

import React, { useRef, useImperativeHandle } from "react"
 
function CustomInput(props, ref) {
  const inputRef = useRef(null)
 
  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus()
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView()
      },
    }
  }, [])
 
  return <input {...props} ref={inputRef} />
}
 
export default React.forwardRef(CustomInput)

Lưu ý:

  • Trong ví dụ trên, thuộc tính current của đối tượng ref là một đối tượng chứa các custom method.
  • Ta cần phải tạo ra một ref riêng biệt để liên kết đến element <input>.

Component cha (App) có thể gọi những custom method mà component con (CustomInput) đã expose:

import { useState, useRef } from "react"
import CustomInput from "./CustomInput"
 
function App() {
  const [value, setValue] = useState("")
  const ref = useRef(null)
 
  function handleClick() {
    ref.current.focus()
    // This won't work because the DOM node isn't exposed:
    // ref.current.style.opacity = 0.5;
  }
 
  return (
    <form>
      <CustomInput type="text" ref={ref} value={value} onChange={(e) => setValue(e.target.value)} />
      <button type="button" onClick={handleClick}>
        Focus
      </button>
    </form>
  )
}
 
export default App

Resources