React Accessing DOM Elements
Table of Contents + −
In the last lesson, the React useRef Hook, you saw a ref is a little box that holds a value across re-renders. Now let’s use its number one job: grabbing a real DOM element so you can focus it, scroll to it, or measure it.
🤔 Why do we need this?
Most of the time React handles the screen for you. But a few jobs need the real element in your hands:
- You want to put the cursor inside an input as soon as a page opens. That’s focus, and React has no state for it.
- You want to scroll the page down to a certain section when a button is clicked.
- You want to measure how wide or tall an element is right now.
- You want to read the current value straight from an input without tracking every keystroke.
None of those can be done by changing state. They need the actual DOM element. So React gives you a clean way to reach it, instead of going around React with something like document.getElementById.
🧩 What is accessing the DOM with a ref?
Accessing the DOM means getting the real HTML element that React put on the page, so you can call browser methods on it. A DOM node is that live element in the page, like the actual <input> the user sees and types into.
- React normally keeps the real DOM hidden from you and updates it itself. That’s a good thing.
- But for a few jobs, like focus or measuring, you need the live element directly.
- A ref is the bridge. You attach it to a JSX element, then React fills it with the matching DOM node for you.
Think of it like a name tag you stick on one element. After React draws the page, you look at the tag and it points you straight to that element.
🛠️ The three steps
Getting a DOM element is always the same small pattern: create the ref, attach it, then use it.
This component creates a ref, attaches it to an input, and is ready to use that input later.
import { useRef } from "react";
function SearchBox() { const inputRef = useRef(null);
return <input ref={inputRef} />;}Let’s read the three steps.
const inputRef = useRef(null)creates the ref. You start it atnullbecause the element doesn’t exist yet.ref={inputRef}attaches the ref to the JSX element. This is the special ref attribute React understands.- After React draws the screen,
inputRef.currentpoints to the real<input>DOM node, ready for you to use.
So you make the box, hand it to the element, and React quietly fills the box with the live node.
⏰ Read myRef.current AFTER render
This is the part people trip on. During the first render the element does not exist yet, so the ref is still empty. You only read it later.
This shows the wrong place to read the ref versus the right place.
function SearchBox() { const inputRef = useRef(null);
// ❌ Wrong - runs during render, the element isn't on the page yet console.log(inputRef.current); // null
function focusInput() { // ✅ Right - runs later, after render, so the node is there inputRef.current.focus(); }
return ( <> <input ref={inputRef} /> <button onClick={focusInput}>Focus the input</button> </> );}Here’s why the timing matters:
- During render React hasn’t created the real
<input>yet, soinputRef.currentis stillnull. Reading it there gives you nothing. - React fills
inputRef.currentwith the DOM node after it draws the screen. - So you read the ref in an event handler or inside an effect, never straight in the body of the component.
The ref is empty during the first render
If you read someRef.current directly in the component body on the first render, you’ll get null and may crash when you call a method on it. Wait for an event or an effect. By then the element exists and the ref points to it.
🎯 Example: a button that focuses an input
Let’s do the classic one. We have an input and a button, and clicking the button drops the cursor into the input.
This form focuses the input when you click the button, using the ref’s DOM node.
import { useRef } from "react";
function SearchBox() { const inputRef = useRef(null);
function handleClick() { inputRef.current.focus(); }
return ( <div> <input ref={inputRef} placeholder="Search..." /> <button onClick={handleClick}>Focus the input</button> </div> );}Let’s walk through the flow.
const inputRef = useRef(null)makes an empty ref.ref={inputRef}ties it to the<input>, so after renderinputRef.currentis the real input element.- When you click the button,
handleClickruns. It callsinputRef.current.focus(), which is a real DOM method on the input. - The cursor jumps into the box, ready for typing. State never changed, so React did not re-render. The browser just focused the element.
focus is a browser method, not a React one
.focus() belongs to the DOM input element, not to React. That’s exactly why you need the real node. The same idea covers .scrollIntoView() to scroll an element into view, or reading inputRef.current.offsetWidth to measure how wide it is.
⚖️ The React way vs reaching into the DOM
Refs are powerful, so it’s easy to overuse them. The rule is simple: only use a ref for things React can’t do with state.
This shows the wrong instinct versus the right one.
// ❌ Wrong - going around React to read a valuefunction handleSubmit() { const value = document.getElementById("email").value; // skips React}
// ✅ Right - let state hold the value, use a ref only for focusfunction handleSubmit() { console.log(email); // value already lives in state inputRef.current.focus(); // ref only for the DOM-only job}Here’s the line to remember:
- For values, text, and what shows on screen, use state. That’s what React is built for, and it keeps the screen in sync.
- Reaching in with
document.getElementByIdgoes around React. The two can fall out of sync and cause hard bugs. - Use a ref only for jobs React has no answer for, like focus, scrolling to an element, or measuring its size.
State for data, refs for DOM-only jobs
A quick test: can you do it by changing state? Then do that. Only when the job is purely about the browser element itself, like focus or scroll, should you reach for a ref.
🧩 What You’ve Learned
- ✅ A ref’s main job is getting a real DOM element so you can call browser methods on it
- ✅ Create the ref with
useRef(null), since the element doesn’t exist yet - ✅ Attach it with the
refattribute on a JSX element, likeref={inputRef} - ✅ After render,
inputRef.currentpoints to the real DOM node - ✅ Read
.currentafter render, in an event handler or an effect, never during render - ✅ Use refs for DOM-only jobs like
.focus(),.scrollIntoView(), or measuring size - ✅ Use state for data and what shows on screen, not
document.getElementById
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What is the number one use of useRef?
Why: The most common use of useRef is to grab the real DOM node, so you can do DOM-only jobs like focus, scroll, or measuring. State is what re-renders the screen, not a ref.
- 2
When can you safely read inputRef.current as the DOM element?
Why: On the first render the element doesn't exist yet, so the ref is null. React fills it after drawing the screen, so you read it in a handler or an effect.
- 3
How do you attach a ref to a JSX input?
Why: You use the special ref attribute: ref={inputRef}. After render, React sets inputRef.current to that real input element.
- 4
Which job is the right reason to use a ref instead of state?
Why: Refs are for DOM-only jobs React can't do with state, like focus or scrollIntoView. Anything about data or what shows on screen should live in state.
🚀 What’s Next?
Now you can grab a real DOM element and call methods on it. Next we’ll zoom in on the most common one of these, putting the cursor where the user needs it, and see the clean patterns for it.