useRef
Reviewed & published by Brayan K
useRef gives you a mutable box — an object { current: value } — that persists across renders but does not trigger a re-render when you change it. It has two everyday jobs: holding a reference to a DOM element (to focus an input or measure a node), and remembering a value (like a timer id or a previous value) without affecting the UI.
Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- What a ref is: a { current } box that persists
- Why changing a ref does NOT re-render
- useRef vs useState — when to use each
- Attach a ref to a DOM node and focus it
- Persist values like timer ids and previous props
- Why a ref is null during the first render
1️⃣ A Ref Is a Persistent Box
useRef(initial) returns an object { current: initial }. The same object survives every render, and you read or write .current directly — no setter. Crucially, writing to it does not schedule a re-render.
function Stopwatch() {
const intervalRef = useRef(null); // { current: null }
function start() {
intervalRef.current = setInterval(tick, 1000); // store the timer id
}
function stop() {
clearInterval(intervalRef.current); // read it back later
}
// ...
}// A ref is just a plain object: { current: value }.
// Changing .current does NOT cause a re-render — it just persists.
const ref = { current: 0 }; // useRef(0) gives you this object
ref.current = ref.current + 1;
ref.current = ref.current + 1;
ref.current = ref.current + 1;
console.log(ref.current); // 3
// In React the screen would NOT update on these changes —
// the value is remembered, but nothing re-renders.2️⃣ Ref vs State
Both useRef and useState remember a value between renders. The difference is the re-render: updating state tells React to re-render; updating a ref does not. Rule of thumb: if a value should appear on screen, use state; if it's bookkeeping the UI doesn't display, use a ref.
// Shows on screen -> state
const [count, setCount] = useState(0);
// Behind the scenes -> ref (no re-render)
const clicksWithoutRender = useRef(0);
clicksWithoutRender.current++; // remembered, but UI doesn't change// REF vs STATE — both persist; only state re-renders.
let renders = 0;
function render() { renders++; }
// Updating a ref: no render
const countRef = { current: 0 };
countRef.current++; // no render
countRef.current++; // no render
console.log("renders after ref updates:", renders); // 0
// Updating state: each commit re-renders
function setState() { render(); }
setState(); // render
setState(); // render
console.log("renders after state updates:", renders); // 2Your turn — build a render counter using a ref's .current:
// 🎯 YOUR TURN — fill in each ___ then press "Try it Yourself".
// A ref is { current: ... }. Build a "render counter" that counts
// how many times a function runs WITHOUT triggering re-renders.
const renderCount = { current: 0 };
function trackRender() {
// 1) Increase the ref's stored number by 1.
renderCount.___ = renderCount.___ + 1; // 👉 use .current twice
return renderCount.current;
}
console.log(trackRender()); // 1
console.log(trackRender()); // 2
console.log(trackRender()); // 3
// ✅ Expected output:
// 1
// 2
// 33️⃣ DOM Refs & Persisting Values
Attach a ref to an element with the ref attribute. After the element mounts, React sets .current to the real DOM node — so you can focus it, scroll it, or measure it. Note it's null during the first render, so read it in an effect or an event handler. Refs are also perfect for remembering a previous value across renders.
function SearchBox() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus(); // focus the input after it mounts
}, []);
return <input ref={inputRef} placeholder="Search..." />;
}// A classic ref use: remember the PREVIOUS value across renders.
// We simulate three renders where the count changes each time.
const prevRef = { current: undefined };
const history = [];
function onRender(count) {
history.push("now=" + count + " prev=" + prevRef.current);
prevRef.current = count; // store for next render (would be in an effect)
}
onRender(0); // now=0 prev=undefined
onRender(5); // now=5 prev=0
onRender(8); // now=8 prev=5
history.forEach(line => console.log(line));🧩 Reorder Challenge
These lines focus an input on mount using a ref. Put them in the right order:
A. useEffect(() => {
B. const inputRef = useRef(null);
C. inputRef.current.focus();
D. }, []);
E. return <input ref={inputRef} />;Common Errors (and the fix)
- "Cannot read properties of null (reading 'focus')." — You read .current during render. Read it in an effect or handler, after mount.
- UI doesn't update when I change a ref. — Correct: refs don't re-render. If it must show on screen, use state.
- Using a ref to store data that drives the UI. — That's a state job. Refs are for non-visual bookkeeping or DOM nodes.
- Forgot to attach the ref. — Add ref={inputRef} to the element, or .current stays null.
📋 Quick Reference
| Task | Code |
|---|---|
| Create a ref | const ref = useRef(initial); |
| Read / write | ref.current |
| Attach to DOM | <input ref={ref} /> |
| Focus an input | ref.current.focus() |
| Store a timer id | ref.current = setInterval(...) |
🧠 Quick Recall
1. You do ref.current++ three times. How many re-renders?
Zero. Changing a ref never triggers a re-render.
2. Why is inputRef.current null on the first render?
The DOM node doesn't exist until after the component mounts. React fills in .current afterward — read it in an effect.
3. A value must appear on screen and update. Ref or state?
State — only state re-renders the UI when it changes.
Mini-Challenge: Click Gap Tracker
Use a ref to remember the time of the last click and report the gap between clicks. Run it and check your output.
// 🎯 MINI-CHALLENGE: a click timestamp tracker
//
// A ref can hold the time of the LAST click without re-rendering.
// 1. Start lastClick = { current: null }.
// 2. For each time in clicks, log "gap: " + (time - lastClick.current)
// — but on the FIRST click (current is null) log "first click".
// 3. After logging, store the time in lastClick.current.
//
// ✅ Expected output:
// first click
// gap: 5
// gap: 3
const clicks = [10, 15, 18];
const lastClick = { current: null };
// your code here🎉 Lesson Complete
- ✅ A ref is a { current } box that persists across renders
- ✅ Changing .current does not re-render
- ✅ Use state for visible data, refs for bookkeeping & DOM nodes
- ✅ Attach with ref={...}; read it in an effect (it's null during render)
Practice quiz
What does useRef return?
- A [value, setValue] pair
- A plain number
- A mutable object of the form { current: value }
- A DOM node directly
Answer: A mutable object of the form { current: value }. useRef(initial) returns a mutable object { current: initial } whose identity is stable across renders.
What happens when you change a ref's .current value?
- Nothing re-renders; the value just persists
- The component re-renders
- React throws an error
- All children re-render
Answer: Nothing re-renders; the value just persists. Changing .current does NOT trigger a re-render. The value is remembered across renders, but the UI does not update.
What is the key difference between useRef and useState?
- useRef is faster to read
- useState cannot hold objects
- useRef cannot persist values
- Updating state re-renders; updating a ref does not
Answer: Updating state re-renders; updating a ref does not. Both persist a value across renders, but setState triggers a re-render while writing to a ref's .current does not.
You do ref.current++ three times. How many re-renders occur?
- Three
- Zero
- One
- Two
Answer: Zero. Changing a ref never triggers a re-render, so the count of re-renders is zero.
How do you attach a ref to a DOM element?
- <input ref={inputRef} />
- <input value={inputRef} />
- <input current={inputRef} />
- useRef(<input />)
Answer: <input ref={inputRef} />. Pass the ref to the element's ref attribute: <input ref={inputRef} />. After mount, React sets inputRef.current to the DOM node.
Why is inputRef.current null during the first render?
- Because useRef starts at null always
- Because refs are read-only
- Because the DOM node does not exist until after the component mounts
- Because React clears it on every render
Answer: Because the DOM node does not exist until after the component mounts. The DOM node is only created after mount, so the ref is null during the first render. Read it in an effect or event handler.
A value must appear on screen and update when it changes. Ref or state?
- Ref, because it is faster
- State, because only state re-renders the UI
- Either works the same
- Ref, then call forceUpdate
Answer: State, because only state re-renders the UI. If a value should be visible and update the UI, use state — only state re-renders when it changes. Refs are for non-visual bookkeeping.
Where should you safely read a DOM ref's .current to call .focus()?
- During render
- In the component's return statement
- Before the ref is created
- In an effect or an event handler, after mount
Answer: In an effect or an event handler, after mount. The DOM node exists only after mount, so read the ref in an effect or event handler — not during render, where it is still null.
Which is a good use case for a ref?
- A counter shown on screen
- Storing a timer id (setInterval) or a previous value without re-rendering
- Text typed into a controlled input that displays live
- A list rendered to the page
Answer: Storing a timer id (setInterval) or a previous value without re-rendering. Refs are ideal for non-visual bookkeeping like timer ids or remembering a previous value — things the UI does not display.
You get 'Cannot read properties of null (reading focus)'. What is the fix?
- Use useState instead of useRef
- Initialize the ref with a DOM node
- Read .current in an effect or handler after mount, not during render
- Remove the ref attribute
Answer: Read .current in an effect or handler after mount, not during render. That error means you read .current during render while it was still null. Read it in an effect or handler, after the element mounts.
Continue this course
- Previous: Checkpoint: React Fundamentals
- Next: useContext & the Context API — Share global state and avoid prop-drilling with the Context API
- Quick reference: React cheat sheet › Effects & Data
Frequently asked questions
What's the difference between useRef and useState?
Both persist a value across renders, but changing a ref's .current does NOT trigger a re-render, while setState does. Use state for data that should update the UI; use a ref for values you want to remember without re-rendering, or to reference a DOM node.
How do I get a DOM element with useRef?
Create a ref with const inputRef = useRef(null), attach it with <input ref={inputRef} />, and after the element mounts React sets inputRef.current to the DOM node. Then you can call inputRef.current.focus().
Why is my ref null?
The DOM node only exists after the component mounts, so inputRef.current is null during the first render. Read it inside an effect or an event handler, not during render.
Can I read a ref to compute what I render?
No — changing a ref doesn't re-render, so the UI won't reflect it. If a value should appear on screen, it belongs in state, not a ref.