Understanding useRef and forwardRef in React — A Beginner’s Guide

When working with React, you often hear about useRef and forwardRef. At first, they can feel confusing: “Why do I need refs when I already have props and state?”

This article will walk you through the why, how, and when of refs in React — with simple examples, analogies, and real use cases.

1. What is useRef?

Think of useRef as a sticky note inside your component. Whatever you write on it will still be there even after React re-renders your component.

function App() {
  const inputRef = React.useRef<HTMLInputElement>(null);

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

  return (
    <div>
      <input ref={inputRef} placeholder="Type here..." />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}
  • useRef creates a box that holds a reference to the <input> DOM element.
  • inputRef.current points to the real HTML element.
  • Clicking the button calls .focus() on the input directly.

Key point: useRef is not just for inputs. You can store any value inside it that survives re-renders. But the most common use is working with DOM elements.

2. What is forwardRef?

Refs work perfectly on built-in HTML tags (<input>, <button>, etc.). But what if you wrap an <input> in your own custom component?

function MyInput(props: React.InputHTMLAttributes<HTMLInputElement>) {
  return <input {...props} />;
}

function App() {
  const inputRef = React.useRef<HTMLInputElement>(null);

  return <MyInput ref={inputRef} />; // ❌ doesn't work
}

Here, the parent tries to pass a ref, but it stops at MyInput. React doesn’t know how to forward refs into custom function components.

That’s where forwardRef comes in.

3. How forwardRef Works

const MyInput = React.forwardRef<
  HTMLInputElement, 
  React.InputHTMLAttributes<HTMLInputElement>
>((props, ref) => {
  return <input {...props} ref={ref} />;
});

MyInput.displayName = "MyInput";

function App() {
  const inputRef = React.useRef<HTMLInputElement>(null);

  React.useEffect(() => {
    inputRef.current?.focus(); // ✅ Works
  }, []);

  return <MyInput ref={inputRef} placeholder="Type here..." />;
}

Now the parent’s ref goes directly to the <input> inside MyInput.

4. Visualizing the Flow

Without forwardRef:

Parent ──ref──▶ [ MyInput ] ✖ stops here
                      └── <input> (unreachable)

With forwardRef:

Parent ──ref──▶ [ MyInput (forwardRef) ] ──▶ <input ref={ref}> ✅

Think of a ref as a power cable:

  • Without forwardRef, the cable gets stuck outside the box.
  • With forwardRef, you drill a hole so the cable reaches the actual device inside.

5. Why We Need Both

  • useRef = creates the cable (a handle to a DOM node).
  • forwardRef = ensures your custom component passes the cable through.

They’re often used together when you build reusable UI components like inputs, buttons, or dropdowns.

6. Real-World Use Cases

  1. Focus an inputref.current?.focus()
  2. Scroll to an elementref.current?.scrollIntoView()
  3. Measure size/positionref.current?.getBoundingClientRect()
  4. Work with libraries → pass refs into custom components for react-hook-form, framer-motion, etc.

7. Beginner-Friendly Analogy

  • useRef = a walkie-talkie to talk directly to a DOM element.
  • forwardRef = a cable extension so custom components don’t block the signal.

8. Takeaway

  • Use useRef to grab DOM elements or store values across renders.
  • Use forwardRef when building custom components that wrap DOM elements.

With these two tools, your React components stay flexible, reusable, and library-friendly.

9. useRef vs useState

At first glance, both seem to “store values.” But they behave very differently.

useState

  • Triggers a re-render when the value changes.
  • Best for values that affect the UI.
function Counter() {
  const [count, setCount] = React.useState(0);
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

useRef

  • Does not trigger re-renders when value changes.
  • Best for mutable values or DOM nodes.
function Stopwatch() {
  const timeRef = React.useRef(0);

  const tick = () => {
    timeRef.current += 1;
    console.log("Current time:", timeRef.current);
  };

  return <button onClick={tick}>Tick</button>;
}

Comparison Table

Feature useState useRef
Causes re-render? ✅ Yes ❌ No
Stores values across renders? ✅ Yes ✅ Yes
Best for UI data (things you want to show) DOM elements, timers, mutable values
Example use Counter, form fields Focus, scroll, intervalId

Rule of Thumb

  • If you want the UI to update when the value changes → use useState.
  • If you just need to hold a value or DOM node → use useRef.

Final Thoughts

- useRef is your silent helper: great for DOM access and values that don’t need UI updates.
- useState is your loud announcer: changes always trigger re-renders.
- forwardRef makes custom components ref-friendly, so they behave like native DOM elements.

Together, these hooks are the glue between React’s declarative UI world and the imperative needs of DOM manipulation.

Comments

Popular posts from this blog

How to Install and Manage PostGIS with a Non-Superuser Role

Leveraging Asynchronous Views in Django REST Framework for High-Performance APIs

Implementing Throttling in Django REST Framework.