DEV Community

Mahdar
Mahdar

Posted on

Why useRef Is Better Than querySelector in React

If you come from a vanilla JavaScript or jQuery background, document.querySelector probably feels natural. You want a DOM node — you grab it. Simple.

But React is not DOM-first.
It’s state-first and declarative.

Using querySelector inside a React app is often a code smell. It might work today, but it silently breaks React’s mental model and makes your code fragile in ways that only show up later.

Let’s talk about why useRef exists, what problem it actually solves, and why it’s the correct tool in React.


React Does Not Own the DOM the Way You Think

In React, you do not control the DOM directly. React does.

React maintains its own abstraction (the Virtual DOM) and decides:

  • when elements are created
  • when they are destroyed
  • when they are replaced
  • when they are re-used

When you use querySelector, you are bypassing React and reaching into the DOM manually. That creates a hidden dependency between your code and something React considers implementation detail.

This leads to subtle bugs.


useRef Is a Contract With React

When you use useRef, you are not “querying” the DOM.

You are saying to React:
“When you create this element, please give me a stable reference to it.”
That’s a contract, not a hack.
React guarantees that:

  • the ref will point to the correct DOM node
  • the reference will stay stable across re-renders
  • the ref lifecycle is aligned with the component lifecycle

This is something querySelector can never guarantee.

With useRef, React assigns the reference only after the element is committed to the DOM.


Concurrent Rendering Changes Everything

React’s concurrent features (like Suspense, transitions, streaming SSR) make DOM querying even more dangerous.

In concurrent rendering:

  • React can render multiple versions of the UI
  • Only one version is committed
  • Others are thrown away

If you use querySelector, you might accidentally interact with a DOM node from a render that never gets committed.

useRef always points to the committed tree — the one the user actually sees.


Performance Is Not the Main Point (But It Helps)
This isn’t really about performance — but it’s worth mentioning.

querySelector:

  • walks the DOM
  • depends on selector complexity
  • can become expensive if abused

useRef:

  • is a direct pointer
  • no lookup
  • constant-time access

More importantly, refs avoid unnecessary reflows and re-queries caused by repeated DOM searches.


Final Thoughts

Using querySelector in React is like bypassing the type system in TypeScript — it works, until it doesn’t.

useRef:

  • respects React’s lifecycle
  • works with concurrent rendering
  • keeps components isolated
  • makes intent explicit
  • scales with your codebase

As your app grows, these things stop being “best practices” and start being requirements.

Write React the way React expects to be written.

Top comments (2)

Collapse
 
vineetgnair profile image
Vineet G Nair

Good one, the point is correct. Even I see devs using the querySelector a lot which is a way to get things done but not the correct way!

Collapse
 
mahdardavari profile image
Mahdar

Thanks working isn’t always same as being correct 🙂