A ref is a plain JavaScript object with one property: { current: value }. React keeps this object for your component and gives you the same object on every render. Changing ref.current does not cause a re-render. You can think of useRef as state that React remembers but never reacts to. The React docs even show that you could build it with useState: const [ref] = useState({ current: initialValue }).
When you write <input ref={inputRef} />, React puts the DOM node in inputRef.current after it updates the page. When the node is removed, React sets it back to null.
Why not just use document.getElementById? Because it doesn't fit how React works:
- Many copies: a component can appear many times on one page, like three
SearchBoxcomponents. Anidmust be unique, andgetElementByIdreturns only the first match. Each ref points to its own copy. - Timing: during render, the DOM node may not exist yet. React fills the ref at the right moment and clears it when the node goes away. With a query, you have to guess when the node exists.
- React owns the DOM: React can remove a node and create a new one, for example when a condition changes or a
keychanges. A node you saved from a query can be an old node that is no longer on the page. A ref always holds the current node. - Clear ownership: a query searches the whole page, so it can grab another component's element by mistake. A ref is a link you pass on purpose. In React 19 you pass it to a child as a normal
refprop. - Server rendering:
documentdoes not exist on the server, so a query during render crashes there. A ref is just an object, so it is safe on the server. - Not only for DOM nodes: a ref can keep any value between renders without re-rendering, like a timer id. A DOM query can't do that.
getElementById still works inside effects and event handlers. It is fine for nodes outside React, like a #modal-root element for portals. Inside your components, use refs.
- A ref is a plain
{ current }object that React keeps for the component across renders. - Changing
ref.currentdoes not re-render the component. - React sets
ref.currentto the DOM node after it updates the page, and tonullwhen the node is removed. - Each component copy gets its own ref, while an
idmust be unique on the page. - React can replace DOM nodes, so a saved query result can be an old node. A ref stays current.
- Refs are safe during server rendering, where
documentdoes not exist. - Refs can also store any value between renders, like a timer id.