You're building a React app. Everything is smooth. You're handling state like a pro, mapping through arrays, and then—boom. You need to focus a text input the second a modal opens. Or maybe you need to measure the exact height of a div to trigger a sticky header. Suddenly, useState feels clunky. It's too slow. It triggers a re-render you don't want.
This is where you meet the "ref."
💡 You might also like: What Does su Stand For? The Command That Actually Runs the Internet
A ref is basically a "trapdoor" in React. It lets you escape the standard data flow to grab a DOM node or store a value that doesn't need to trigger a visual update. Honestly, it’s one of the most misunderstood parts of the library. People either use it for everything or are terrified to touch it.
The Mental Model: State vs. Refs
Think of state as a scoreboard. When the score changes, the whole stadium (your component) needs to know about it so they can react. But a ref? A ref is like a coach's private notebook. The coach can scribble a note, change a strategy, or look at a player's stats without the fans in the stands ever seeing a change on the big screen.
Technically, a ref is just a plain JavaScript object. It has one property: current.
When you use the useRef hook, React gives you this object. You can change ref.current to whatever you want—a string, a number, or a reference to a physical HTML element. The magic (or the danger) is that changing it is a "mutation." Unlike state, mutating a ref doesn't tell React to re-render the component.
When You Actually Need a Ref
Don't use them just because you're lazy. React's declarative nature—where you describe what the UI should look like based on state—is its biggest strength. If you start bypassing that with refs, you're basically writing jQuery inside React. It gets messy fast.
However, there are three main times you'll definitely need them:
- Managing Focus, Text Selection, or Media Playback. If you've ever tried to programmatically focus an input field using only state, you know it’s a nightmare. You need a direct line to the
.focus()method on the HTML element. - Integrating with Third-Party DOM Libraries. If you're using D3.js, Google Maps, or a jQuery plugin (hey, it happens), those libraries want to "own" the DOM node. You use a ref to hand the node over to them.
- Storing "Instance Variables" That Don't Affect the UI. Timers are a classic example. If you have a
setIntervalID, you need to store it so you can clear it later. Putting that in state is overkill because the user doesn't care what the ID number is.
The "Escape Hatch" Reality
Dan Abramov, one of the primary creators of Redux and a long-time React core team member, famously refers to refs as an "escape hatch." You use them when the "React way" just won't cut it.
But wait. There's a catch.
Because refs don't trigger re-renders, you can't use them to display information on the screen. If you try to render {myRef.current} in your JSX, it might show an old value, or nothing at all, because React doesn't know it needs to update the view when that value changes.
How to Set One Up (The Right Way)
In a functional component, it looks like this:
const myInputRef = useRef(null);
// Later in your JSX
<input ref={myInputRef} type="text" />
When the component mounts, React sets myInputRef.current to the actual <input> DOM element. Now you can call myInputRef.current.focus() whenever you want. Simple. Sorta.
Common Pitfall: The "Ref is Null" Error
I see this all the time. Someone tries to access myRef.current inside the main body of the component function.
Don't do that.
During the first render, the DOM node hasn't been created yet. current will be null. You have to wait until the component has "mounted." This is why you almost always see refs being accessed inside a useEffect hook or an event handler.
📖 Related: Why Obfuscate Matters: How We Hide the Truth in Code and Conversation
Advanced Moves: Forwarding Refs
What if you're building a custom component, like a <MyStyledInput />, and the parent component needs to focus the input inside it?
By default, you can't just pass a ref prop to a functional component. It’ll just be ignored. You have to use forwardRef.
const MyStyledInput = forwardRef((props, ref) => {
return <input ref={ref} className="very-cool-style" {...props} />;
});
This "forwards" the ref from the parent down into the child's DOM node. It’s a bit of boilerplate, but it’s essential for building reusable UI libraries.
The Secret Life of "Ref" as a Variable Store
Most people think refs are only for DOM nodes. They aren't.
You can use useRef to keep track of a previous value. Since refs persist across renders but don't cause them, you can store the "old" value of a prop or state inside a useEffect and compare it to the "new" value on the next render.
Is it hacky? Maybe. Is it useful? Absolutely.
A Warning on Overuse
I once worked on a project where the lead dev decided that since "state is slow" (it's not, usually), they would manage all form inputs using refs. They called it "uncontrolled components."
Technically, it worked. The app was snappy. But validation was a nightmare. Syncing the UI was impossible. We ended up with a codebase that felt like 2012-era spaghetti.
The rule of thumb: If you can do it with state, do it with state.
Specific Real-World Example: The "Click Outside" Hook
One of the most common uses for a ref is detecting when a user clicks outside a dropdown menu to close it. You attach a ref to the dropdown container. Then, you add a global click listener to the document.
When a click happens, you check if the target of that click is "contained" within your ref's DOM node. If it isn't, you close the menu.
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsOpen(false);
}
This is the kind of stuff you just can't do cleanly without refs.
Actionable Next Steps
To truly master refs, stop reading and start breaking things. Here is a quick progression to get you there:
- Step 1: The Auto-Focus. Build a simple form where the first input field focuses automatically when the page loads. Use
useRefanduseEffect. - Step 2: The Video Controller. Create a custom video player. Use a ref to target the
<video>tag and create your own "Play" and "Pause" buttons that trigger.play()and.pause()on the ref. - Step 3: The Scroll Listener. Build a component that changes its background color only when the user has scrolled the element itself past a certain point. Use a ref to measure
scrollTop. - Step 4: The Forwarding Challenge. Wrap a basic HTML input in a custom component and try to focus it from a parent component using
forwardRef.
Understand that a ref is a tool for the "physical" side of the web—the actual elements on the screen. State is for the "data" side. Keep those two separate in your head, and you'll stop fighting React and start making it work for you.