Using document.querySelector instead of useRef in React

Loading

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

  1. Breaks Component Encapsulation
    Direct DOM queries reach outside React’s virtual DOM and component hierarchy
  2. Fragile Selectors
    Relies on CSS selectors that can break with DOM/styling changes
  3. Timing Issues
    Elements might not exist yet when your query runs
  4. Performance Impact
    DOM queries are slower than React’s ref system
  5. 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

useRefdocument.querySelector
React IntegrationFully integratedOutside React system
TimingSafe after renderMight run too early
PerformanceOptimizedSlower
Component BoundariesRespects componentsBreaks encapsulation
SSRWorksFails

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.

Leave a Reply

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