Ref Hooks

Refs let a component hold some information that isnt used for rendering and they are useful when you need to work with non-React systems.


  • useRef You can hold any value in it, but most often its used to hold DOM node.
  • useImperativeHandle lets you customize the ref exposed by your component.

useRef

Used for saving DOM node and timeout ID.

Caveats

  • The ref.current property can mutate. Unlike state, it is mutable. However, if it holds an object that is used for rendering, then you shouldn’t mutate that object.

  • Changing a ref does not trigger a re-render. React is not aware of when you change it because a ref is a plain JavaScript object.

  • Do not write or read ref.current during rendering, except for initialization. This makes your component’s behavior unpredictable. (example: don'ts)

Example: Don'ts

function Main() {
  useEffect(() => {
    // You can read or write refs in effects
    ref.current = "something";
  });

  function handleClick() {
    // You can read or write refs in event handlers
    doSomething(myOtherRef.current);
  }
}
function Main() {
  // Don't write a ref during rendering
  ref.current = "something";

  // Don't read a ref during rendering
  return <h1>{myOtherRef.current}</h1>;
}

Example: basic

const intervalRef = useRef(null);

function handleStart() {
  setStartTime(Date.now());
  setNow(Date.now());

  clearInterval(intervalRef.current);
  intervalRef.current = setInterval(() => {
    setNow(Date.now());
  }, 10);
}

useImperativeHandle

If you don’t want to expose the entire <input> DOM node, but you want to expose two of its methods: focus and scrollIntoView. To do this, keep the real browser DOM in a separate ref. Then use useImperativeHandle to expose a handle with only the methods that you want the parent component to call

Prior to React 19, exposing a ref from a function component required wrapping it in forwardRef. As of React 19, forwardRef is deprecated ref can be received as a regular prop instead, as shown below. See forwardRef for the old-vs-new comparison.

Do not overuse refs. You should only use refs for imperative behaviors that you can’t express as props: for example, scrolling to a node, focusing a node, triggering an animation, selecting text, and so on.

If you can express something as a prop, you should not use a ref. For example, instead of exposing an imperative handle like { open, close } from a Modal component, it is better to take isOpen as a prop like <Modal isOpen={isOpen} />. Effects can help you expose imperative behaviors via props.

Parameters

  • The ref you received as the second argument from the forwardRef render function.

  • A function that takes no arguments and returns the ref handle you want to expose.

  • The list of all reactive values referenced inside of the function. If a re-render resulted in a change to some dependency, or if you omitted this argument, this function will re-execute, and the newly created handle will be assigned to the ref.

Example: basic

import { useImperativeHandle, useRef } from "react";

function MyInput({
  ref,
  ...props
}: React.ComponentProps<"input"> & { ref?: React.Ref<{ focus: () => void; scrollIntoView: () => void }> }) {
  const inputRef = useRef<HTMLInputElement>(null);

  useImperativeHandle(
    ref,
    () => {
      return {
        focus() {
          inputRef.current.focus();
        },
        scrollIntoView() {
          inputRef.current.scrollIntoView();
        },
      };
    },
    []
  );

  return <input {...props} ref={inputRef} />;
}

export default MyInput;
import { useRef } from "react";

import MyInput from "./MyInput.js";

export default function Form() {
  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>
      <MyInput placeholder="Enter your name" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}