Why This Is Problematic
Using document.querySelector
in React components is generally considered an anti-pattern because:
- Breaks React’s Virtual DOM abstraction – Direct DOM manipulation bypasses React’s reconciliation process
- Causes synchronization issues – Selected elements might not match React’s rendered output
- Leads to fragile selectors – Relies on class/ID naming that might change
- Performance overhead – DOM queries are more expensive than refs
- SSR incompatibility – Will fail during server-side rendering
When useRef
Is the Better Choice
function MyComponent() {
const inputRef = useRef(null);
useEffect(() => {
// Access the DOM element safely
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return <input ref={inputRef} />;
}
Proper Ref Usage Patterns
- Accessing DOM elements:
const buttonRef = useRef(null);
<button ref={buttonRef}>Click</button>
- Managing focus:
useEffect(() => {
inputRef.current?.focus();
}, []);
- Measuring elements:
useEffect(() => {
const width = divRef.current?.clientWidth;
}, []);
When You Might Still Need querySelector
- Accessing elements outside your component (modals, portals)
- Working with third-party libraries that require DOM nodes
- Selecting elements you don’t control
Best Practices
- Always prefer
useRef
for elements you render - Use ref callbacks for dynamic refs:
<div ref={(el) => { if (el) /* do something */ }} />
- Combine with
useEffect
for safe access - Consider custom hooks for common ref patterns:
function useFocus() {
const ref = useRef(null);
useEffect(() => ref.current?.focus(), []);
return ref;
}
Performance Comparison
Approach | Performance Impact | React Compatibility | SSR Safe |
---|---|---|---|
useRef | Low | Full | Yes |
querySelector | High | Partial | No |