When to Avoid useRef Hook in React
The useRef hook in React is a powerful tool designed to
persist values across renders without triggering a re-render, as well as
to directly access DOM elements. However, because it bypasses React’s
standard state management, misusing it can lead to UI bugs, inconsistent
states, and code that is difficult to debug. This article outlines the
specific scenarios where you should avoid using the useRef
hook and what you should use instead.
1. When Storing Values That Affect the UI
The most common mistake is using useRef to store data
that needs to be displayed on the screen. Because updating a ref’s
.current property does not trigger a component re-render,
any UI changes dependent on that ref will not reflect on the screen
until some other event forces a render.
- Avoid: Using a ref to store toggle states, form input values that need validation messages, or count values displayed in the HTML.
- Solution: Use
useStateoruseReducer. These hooks ensure that whenever the data changes, React schedules a re-render to keep the UI in sync with your data.
2. Reading or Writing Refs During Rendering
React operates under the assumption that your component’s render
function is a pure function. It should take props and state and return
JSX without causing side effects. Reading or writing to
ref.current during the render phase introduces side effects
and can lead to unpredictable UI behavior, especially with React’s
concurrent rendering features.
- Avoid: Mutating or accessing
myRef.currentdirectly in the body of your component function during render. - Solution: Only read or write to refs inside event
handlers (like
onClick) or withinuseEffecthooks. If you need a value during render, it should be part of the component’s state or props.
3. Manipulating DOM Nodes Managed by React
While useRef is the correct tool for accessing DOM nodes
(such as focusing an input or measuring an element’s size), you should
avoid using it to modify DOM nodes that React is actively managing.
Manually appending children, removing elements, or changing classes via
element.classList can conflict with React’s virtual DOM
reconciliation.
- Avoid: Using
ref.current.appendChild(),ref.current.remove(), or manually updating text content. - Solution: Let React handle the DOM structure and
styling declaratively. Use state to conditionally render components or
toggle CSS classes. Only use refs for non-destructive DOM operations
like calling
.focus(),.scrollIntoView(), or playing/pausing media.
4. Replacing standard Props and Context for Data Flow
Sometimes developers use refs to pass data up, down, or sideways through the component tree to avoid “prop drilling” or re-renders. Bypassing React’s unidirectional data flow makes your application architecture fragile and harder to maintain.
- Avoid: Passing refs deep down the tree to imperatively trigger actions on child components.
- Solution: Use standard React props for parent-to-child communication, lifting state up for sibling communication, or the React Context API / state management libraries for global state.