In the world of React, managing state and component lifecycle are core concepts. We're all familiar with useState for state management and useEffect for handling side effects. But there's another hook, quieter but incredibly powerful: useRef.

If you've ever wondered how to directly access a DOM element, or needed to store a value without triggering a re-render every time it changes, then useRef is exactly what you're looking for.
What is useRef? An Overview
Basically, useRef is a React hook that lets you create a mutable "ref" object. This object has a single property: .current.
The syntax is simple:
import React, { useRef } from 'react'
const myRef = useRef(initialValue)
The most important thing about useRef is: Changing the value of myRef.current does NOT trigger a component re-render. This is the key difference from useState.
Think of useRef as a little box where you can store anything. You can change what's inside the box (.current) at any time, but because the box is "opaque" to React's rendering system, the component doesn't "care" about those changes.
Common use cases for useRef
There are two main scenarios where useRef shines—solving problems that useState can't or shouldn't handle.
1. Directly Accessing DOM Elements
This is the most common and powerful use of useRef. In React, we're encouraged to think "declaratively" rather than "imperatively." However, sometimes you need to interact directly with the DOM, such as:
- Automatically focusing an input when a component mounts.
- Managing animations or transitions.
- Integrating with third-party libraries that require a direct DOM reference (e.g., a chart library).
- Measuring the size or position of an element.
How it works:
- Create a ref:
const myInputRef = useRef(null); - Attach the ref to a JSX element via the
refattribute:<input ref={myInputRef} type="text" /> - Now,
myInputRef.currentpoints directly to the DOM node of the<input>. You can access all its properties and methods.
Classic example: Auto-focusing an input
import React, { useRef, useEffect } from 'react'
function AutoFocusInput() {
// 1. Create the ref
const inputRef = useRef(null)
useEffect(() => {
// 3. After the component mounts, call focus() on the input
if (inputRef.current) {
inputRef.current.focus()
}
}, []) // Empty array ensures this runs only once after mount
return (
<div>
<label>Your name:</label>
{/* 2. Attach the ref to the input */}
<input ref={inputRef} type="text" placeholder="Enter your name..." />
</div>
)
}
2. Storing a Mutable Value Without Causing Re-render
Sometimes you need a value to persist across renders, you need to update it, but you don't want the component to re-render every time it changes. useState won't help here, but useRef is perfect.
The value in ref.current is preserved across renders.
Common use cases:
- Storing timer IDs: like
setTimeoutorsetInterval. - Storing the previous value of a state or prop.
- Counting how many times a component has rendered.
Example: Counting renders
import React, { useState, useEffect, useRef } from 'react'
function RenderCounter() {
const [count, setCount] = useState(0)
// Create a ref with initial value 0
const renderCount = useRef(0)
useEffect(() => {
// Increment the ref value after every render.
// This does NOT cause an infinite render loop!
renderCount.current = renderCount.current + 1
}) // No dependency array, so this runs after every render
return (
<div>
<p>Current state (count): {count}</p>
<button onClick={() => setCount((c) => c + 1)}>Increment Count</button>
<p>This component has rendered: {renderCount.current} times.</p>
</div>
)
}
In this example, every time you click "Increment Count", the count state changes and the component re-renders. useEffect runs, and renderCount.current is updated. Because updating the ref doesn't cause a re-render, you avoid an infinite loop, which would happen if you used useState to store the render count.
useRef vs. useState: When Should You Use Each?
This is a core question for becoming a skilled React developer. Remember this simple rule:
| Feature | useState | useRef |
|---|---|---|
| Causes Re-render? | YES ✅ | NO ❌ |
| Update timing | Asynchronous | Synchronous |
| Main purpose | Manage state that affects the UI | Access DOM or store data not directly affecting the UI |
| When to use? | When you want changes to be reflected on the screen | When you need a "persistent box" for data across renders without triggering re-renders |
Memory tip: Ask yourself, "Does the UI need to change when this value changes?" If YES, use
useState. If NO,useRefis likely the better choice.
"Forwarding" Refs with forwardRef
Sometimes, you want a parent component to access a DOM element deep inside a child component. By default, you can't pass a ref directly to a custom component. This is where React.forwardRef comes in.
forwardRef is a higher-order component that lets your component "receive" a ref from its parent and "forward" it to a specific DOM element inside.
Example:
import React, { useRef, forwardRef, useImperativeHandle } from 'react'
// Child component wrapped in forwardRef
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef()
// (Advanced) Only expose the `focus` method to the parent
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus()
},
shake: () => {
// Add shake animation here
console.log('Shaking the input!')
},
}))
return <input ref={inputRef} placeholder={props.placeholder} />
})
// Parent component
function App() {
const fancyInputRef = useRef()
const handleClick = () => {
// Now you can call the exposed methods
fancyInputRef.current.focus()
fancyInputRef.current.shake()
}
return (
<>
<FancyInput
ref={fancyInputRef}
placeholder="Click the button to focus..."
/>
<button onClick={handleClick}>Focus the Input</button>
</>
)
}
In this example, the App component can directly control FancyInput via the forwarded ref. The useImperativeHandle hook lets you customize and limit what the parent can access, improving encapsulation.
Conclusion: useRef is More Than Just a Tool
useRef isn’t just a way to "escape" React’s model and interact with the DOM. It’s a powerful and flexible hook that gives you a way to handle mutable values without being tied to the render lifecycle.
Understanding when and why to use useRef instead of useState will level up your React thinking, help you write more efficient and optimized code, and solve complex problems elegantly.
Next time you need to store data "silently," remember your trusty companion: useRef!
![[React Basics] Mastering the Most Important Design Patterns](/images/blog/react-design-patterns.webp)
![[React Basics] Everything About Redux in React: Definition, Principles and Detailed Examples](/images/blog/redux-in-react.webp)
![[React Basics] Using TypeScript with React: The Big Benefits](/images/blog/typescript-with-react.webp)