Using document.querySelector instead of useRef in React

Loading

When working with DOM elements in React, you have two main approaches: using document.querySelector() (the traditional JavaScript way) or useRef (the React way). Here’s when and how to use each, along with key differences:

1. document.querySelector() in React

import { useEffect } from 'react';

function Component() {
  useEffect(() => {
    const button = document.querySelector('#my-button');
    button.addEventListener('click', () => {
      console.log('Button clicked!');
    });

    return () => button.removeEventListener('click'); // Cleanup
  }, []);

  return <button id="my-button">Click Me</button>;
}

When to Use It:

  • Legacy Integration (e.g., third-party libraries that need raw DOM access)
  • One-Time DOM Query (e.g., initializing a non-React library like Chart.js)
  • Global Selectors (e.g., document.body)

Problems:

  • Breaks React’s Virtual DOM (direct DOM manipulation can cause conflicts)
  • No React State Awareness (changes won’t trigger re-renders)
  • Unreliable in Strict Mode (double-renders can break selectors)

2. useRef (The React Way)

import { useRef } from 'react';

function Component() {
  const buttonRef = useRef(null);

  useEffect(() => {
    if (buttonRef.current) {
      buttonRef.current.addEventListener('click', () => {
        console.log('Button clicked!');
      });
    }

    return () => {
      if (buttonRef.current) {
        buttonRef.current.removeEventListener('click');
      }
    };
  }, []);

  return <button ref={buttonRef}>Click Me</button>;
}

When to Use It:

  • Managed DOM Access (works with React’s lifecycle)
  • Dynamic Elements (refs update with re-renders)
  • Performance (avoids DOM re-scans on every render)

Key Benefits:

  • 🚀 React-Compatible: Survives re-renders without stale references
  • 🧹 Automatic Cleanup: Easier to integrate with useEffect cleanup
  • 📌 Component-Scoped: Avoids global DOM pollution

3. Key Differences

Featuredocument.querySelector()useRef
React Integration❌ Bypasses Virtual DOM✅ Fully integrated
Re-Render Safety❌ May break on updates✅ Stable across re-renders
ScopeGlobal DOMComponent instance only
PerformanceSlower (re-scans DOM)Faster (direct reference)
Use CaseLegacy/third-party libsModern React apps

4. Best Practice

  • Prefer useRef for most React projects (safer, more performant)
  • Only use document.querySelector when:
  • Integrating non-React libraries
  • Accessing elements outside your component (e.g., document.body)
  • Debugging in dev tools (temporary use)

Example Where querySelector is Acceptable:

useEffect(() => {
  // One-time initialization of a jQuery plugin
  const $element = document.querySelector('.third-party-widget');
  $(element).pluginInit();
}, []);

Example Where useRef is Better:

const inputRef = useRef(null);

const focusInput = () => {
  inputRef.current.focus(); // More reliable than querySelector
};

return <input ref={inputRef} />;

5. Advanced Pattern: Forwarding Refs

For reusable components:

const Button = React.forwardRef((props, ref) => (
  <button ref={ref}>{props.children}</button>
));

// Parent component
function Parent() {
  const buttonRef = useRef(null);
  return <Button ref={buttonRef}>Click</Button>;
}

Final Verdict

  • 95% of the time: Use useRef (React-optimized, cleaner code)
  • 5% edge cases: Use document.querySelector (escape hatch for special needs)

Leave a Reply

Your email address will not be published. Required fields are marked *