![]()
Why document.querySelector is Problematic in React (and What to Use Instead)
Using document.querySelector in React components is generally an anti-pattern that can lead to several issues. Here’s why you should prefer useRef and how to make the transition:
The Problems with document.querySelector
- Breaks Component Encapsulation
Direct DOM queries reach outside React’s virtual DOM and component hierarchy - Fragile Selectors
Relies on CSS selectors that can break with DOM/styling changes - Timing Issues
Elements might not exist yet when your query runs - Performance Impact
DOM queries are slower than React’s ref system - SSR Incompatibility
Will fail during server-side rendering
Proper React Alternatives
1. Basic useRef Example
function MyComponent() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
2. Refs with Dynamic Elements
function DynamicList() {
const itemRefs = useRef([]);
useEffect(() => {
// Access a specific element after render
if (itemRefs.current[2]) {
itemRefs.current[2].scrollIntoView();
}
}, []);
return (
<ul>
{items.map((item, index) => (
<li
key={item.id}
ref={el => itemRefs.current[index] = el}
>
{item.name}
</li>
))}
</ul>
);
}
3. Forwarding Refs for Custom Components
// Custom component
const CustomInput = forwardRef((props, ref) => (
<input ref={ref} {...props} />
));
// Parent component
function Parent() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <CustomInput ref={inputRef} />;
}
When You Might (Carefully) Use DOM Queries
There are rare cases where direct DOM access might be necessary:
useEffect(() => {
// Only when you truly need elements outside React's control
const externalElement = document.querySelector('#legacy-widget');
if (externalElement) {
// Do something with it
}
}, []);
Key Differences
useRef | document.querySelector | |
|---|---|---|
| React Integration | Fully integrated | Outside React system |
| Timing | Safe after render | Might run too early |
| Performance | Optimized | Slower |
| Component Boundaries | Respects components | Breaks encapsulation |
| SSR | Works | Fails |
Migration Example
Before (Bad):
function OldComponent() {
const handleClick = () => {
const el = document.querySelector('.my-input');
el.focus();
};
return (
<div>
<input className="my-input" />
<button onClick={handleClick}>Focus</button>
</div>
);
}
After (Good):
function NewComponent() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} />
<button onClick={handleClick}>Focus</button>
</div>
);
}
Always prefer React’s ref system unless you have a specific need to interact with non-React DOM elements.
