Forward Refs and useRef for DOM Access in React

Table of Contents

  1. Introduction
  2. Understanding the useRef Hook
  3. Common useRef Use Cases
  4. Accessing DOM Elements with useRef
  5. What is forwardRef in React?
  6. When to Use forwardRef
  7. How to Use forwardRef in Practice
  8. Combining forwardRef and useImperativeHandle
  9. Practical Example: Creating a Custom Input with Focus
  10. Best Practices and Gotchas
  11. Conclusion

1. Introduction

React promotes a declarative approach to building UIs, which typically reduces the need for directly accessing the DOM. However, there are legitimate scenarios—like focusing an input, scrolling to a div, or measuring dimensions—where imperative access to DOM elements is necessary. That’s where useRef and forwardRef become essential.

This module covers everything you need to know about refs in React, including how to use them effectively, when to avoid them, and how forwardRef enables passing refs through component hierarchies.


2. Understanding the useRef Hook

The useRef hook is part of React’s Hooks API. It creates a mutable object that persists for the entire lifetime of the component and does not trigger re-renders when it changes.

Syntax:

jsxCopyEditconst myRef = useRef(initialValue);
  • myRef.current holds the mutable value.
  • Commonly used to store DOM elements or mutable values that don’t need to cause a re-render.

3. Common useRef Use Cases

  • Accessing DOM nodes directly (e.g., .focus() on an input)
  • Storing mutable values like timers, previous state values, or third-party instances
  • Avoiding re-renders when storing non-state data
  • Integrating with non-React libraries that require a DOM element reference

4. Accessing DOM Elements with useRef

Here’s a basic example to programmatically focus an input field:

jsxCopyEditimport React, { useRef } from 'react';

function FocusInput() {
  const inputRef = useRef(null);

  const handleClick = () => {
    inputRef.current?.focus();
  };

  return (
    <>
      <input ref={inputRef} type="text" placeholder="Click button to focus me" />
      <button onClick={handleClick}>Focus Input</button>
    </>
  );
}

Here, inputRef.current refers to the actual DOM node of the <input> element.


5. What is forwardRef in React?

By default, refs do not pass through components. If you try to attach a ref to a component, React will attach it to the component instance, not its inner DOM node.

To forward a ref from a parent to a child’s DOM node, use forwardRef, a higher-order component provided by React.


6. When to Use forwardRef

Use forwardRef when:

  • Creating reusable component libraries
  • Wrapping third-party components
  • Composing custom inputs, buttons, modals, or form controls
  • You want to expose internal DOM nodes or methods to parent components

7. How to Use forwardRef in Practice

Here’s how to create a button that forwards the ref to its DOM node:

jsxCopyEditimport React, { forwardRef } from 'react';

const CustomButton = forwardRef((props, ref) => (
  <button ref={ref} className="custom-button">
    {props.children}
  </button>
));

export default CustomButton;

Now you can use it like:

jsxCopyEditconst App = () => {
  const buttonRef = useRef();

  return (
    <>
      <CustomButton ref={buttonRef}>Click Me</CustomButton>
    </>
  );
};

Now buttonRef.current refers to the underlying <button> DOM element.


8. Combining forwardRef and useImperativeHandle

React also provides useImperativeHandle to expose custom methods from a component to its parent using ref.

Example:

jsxCopyEditconst FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focusInput: () => {
      inputRef.current.focus();
    }
  }));

  return <input ref={inputRef} type="text" />;
});

And in the parent:

jsxCopyEditconst App = () => {
  const fancyInputRef = useRef();

  return (
    <>
      <FancyInput ref={fancyInputRef} />
      <button onClick={() => fancyInputRef.current.focusInput()}>Focus</button>
    </>
  );
};

This makes your components more encapsulated and controlled while still allowing imperative actions.


9. Practical Example: Creating a Custom Input with Focus

Let’s combine forwardRef, useRef, and useImperativeHandle:

jsxCopyEditconst CustomInput = forwardRef((props, ref) => {
  const internalRef = useRef();

  useImperativeHandle(ref, () => ({
    focus: () => internalRef.current.focus(),
    clear: () => internalRef.current.value = ''
  }));

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

const App = () => {
  const inputRef = useRef();

  return (
    <>
      <CustomInput ref={inputRef} placeholder="Custom input" />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
      <button onClick={() => inputRef.current.clear()}>Clear</button>
    </>
  );
};

10. Best Practices and Gotchas

  • Avoid overusing refs—prefer state and props for data flow.
  • Use useRef for non-reactive data like timers or cached values.
  • Wrap components with forwardRef only when DOM access is required.
  • Combine with useImperativeHandle to expose minimal and safe APIs.
  • Ensure ref safety with optional chaining (ref?.current?....).

11. Conclusion

useRef and forwardRef are critical for scenarios where React’s declarative model needs to interact with imperative browser APIs or third-party libraries. When used judiciously, they offer powerful control over components without breaking the unidirectional data flow principle.

Summary:

  • useRef is for persistent references that don’t cause re-renders.
  • forwardRef enables passing a ref through a component to its children.
  • Combine with useImperativeHandle to expose custom actions to the parent.