When to Avoid React Refs in React

React refs are a powerful feature that allow direct access to DOM nodes or persist values across renders without triggering a re-render. However, because they bypass React’s declarative programming model, using them inappropriately can lead to bugs, inconsistent user interfaces, and difficult-to-maintain code. This article explains the key scenarios where you should avoid using React refs and opt for state management instead.

1. For Directing UI Updates and Rendering

You should never use refs to store information that is directly rendered on the screen. Because updating a ref’s .current property does not trigger a component re-render, any UI changes dependent on that value will not reflect in the browser.

If a piece of information is used to determine what to display on the screen, use useState or useReducer instead. Save refs strictly for values that do not affect the visual output of the component, such as timer IDs or previous state values.

2. For Managing Form Inputs (In Most Cases)

While “uncontrolled components” use refs to pull values from form fields when submitted, they should generally be avoided if you need real-time feedback.

Avoid using refs for form elements if you require: * Instant validation: Checking if an email is valid as the user types. * Conditional UI: Disabling a submit button until all fields are filled. * Dynamic inputs: Formatting credit card numbers with spaces automatically.

For these interactive behaviors, use controlled components with state (useState) to ensure React remains the single source of truth for the input values.

3. For Modifying Styling and DOM Layout

It is tempting to use a ref to directly alter a DOM node’s class list or inline styles (e.g., myRef.current.style.display = 'none'). However, doing so bypasses React’s virtual DOM, which can cause synchronization issues between your code and what React thinks is rendered.

Instead, control styling declaratively. Use state variables to dynamically apply CSS classes or inline style objects:

// Avoid this:
myRef.current.classList.add('active');

// Do this:
const [isActive, setIsActive] = useState(false);
return <div className={isActive ? 'active' : ''} />;

4. For Breaking the Top-Down Data Flow

React relies on a strict parent-to-child, top-down data flow. Using refs to expose functions or state from a child component to a parent (often done via useImperativeHandle) breaks this encapsulation.

If a parent component needs access to a child’s state or needs to trigger an action inside a child, you should lift the state up to the parent component. Pass the state and callback functions down as props instead of trying to manipulate the child component directly using a ref.