Ref in React : Remote access to DOM elements

Samyak Jain - Apr 9 - - Dev Community

Ever struggled with making two unrelated components in React interact smoothly without cluttering your code? You're not alone. Let's explore a clean, efficient solution

Let's say you created a file input element and clicking on it does your work but then you also have a button which when clicked on, you want the same functionality. But don't want to wrap the button in the input tag as well because it's already defined. You may need to focus on a node, scroll to it, or measure its size and position.

Many of you must be familiar with these use cases from vanilla JS, where it provides functions like getElementByID and you do same things like these and you can surely use the same in React as well.

BUT

It is not recommended to mix vanilla JavaScript DOM manipulation methods like getElementById with React applications. This is because React manages the DOM differently through its virtual DOM and reconciliation process.

React's declarative approach encourages managing the DOM through state and props and updating the UI in response to changes in state.

Directly manipulating the DOM using methods like getElementById can lead to inconsistencies between the virtual DOM managed by React and the actual DOM, potentially causing unexpected behavior and bugs in your application.

That's where the Refs comes into the picture in React. I think of refs as a way to remotely access an element from another element, even when the other element is not related to the first element in any way.

If I had to give an analogy of refs, Refs are like a way to turn your fan ON through your mobile phone when traditionally you can do that only through the button which is connected to the fan.

It provides us a way to interact with elements and store their information, I can add a ref attribute on a div and store its reference in a useRef hook and then call it in the onClick listener of another div, which is not related to the previous div in any way.

Here is a sandbox link where I used a useRef to trigger a file Input field through a button.

You’ll see that when you select a file either with the input button or the custom button, you’ll see the message ‘I got called’ when you select a file because we are just accessing the reference of the original input field through the custom button.

But why should I use only useRef for this purpose? Why I shouldn't use a useState for this? I mean we are just storing the reference of an element right?

Well, the main reason is simply that useState is not designed for this use case, State (useState) is used for data that, when changed, should re-render the component. State is reactive; changes to state variables trigger component re-renders.

State is intended for data that, when changed, should update the UI. However, a DOM element reference (like an input field) doesn't inherently require UI updates when set.

Each time the ref changes (which can be often, due to the nature of inline function refs), it causes a state update and thus a re-render of the component. This is inefficient and unnecessary for simply referencing a DOM element.

Thats why useRef is the better option here because making any change in the useRef-based variable does not cause a re-render.

Ok, so we get it that it's against React’s design principles to use useState for this purpose, but still if I totally neglect this reasoning and still decide to use useState for storing reference, what harm can it cause to the basic working of my feature?

Delay in Availability
State updates in React are asynchronous. When you update a state with a new DOM reference, there's a brief period before the state is actually updated. This can lead to timing issues where you try to access the ref before the state has updated, potentially leading to errors or undefined values.

Here is a sandbox link which demonstrates using ref with useState and useRef and explains why you should use useRef for storing references instead of useState.

Now if you checked the working example, you can see that The inputRef might not be immediately available after setting due to React's asynchronous state updates. This could lead to timing issues or undefined values when trying to access or interact with the input element right after it's mounted.

Ref attribute looks cool, right? But over usage of this ref attribute and using it for everything is again a bad idea, just like useState didn’t fit in ref’s use case same way ref doesn't fits every where else as well.

So when to use Ref generally?

When you need direct access to a DOM element to perform actions that cannot be done declaratively through React's state system, ref is the tool to use. Common use cases include:

  1. Focusing an Input - Programmatically setting focus on an input field when a component mounts or in response to specific user interactions.
  2. Measuring Elements - Obtaining measurements (e.g., width, height, or position) of an element that are only available via the DOM API.
  3. Managing Focus, Text Selection, or Media Control ref allows you to manage focus, select text, or control media playback (play, pause, seek) in a way that is not possible through declarative state updates alone.

Now these examples are just some examples which were top of my mind, there can be a lot of creative ways to use ref attribute.

Now that we have understood about refs, their use cases, and all the other considerations, let's see how else we can use it, the most popular use of refs in React is through ForwardRefs

ForwardRefs

Forwarding refs in React is a technique that allows you to pass a ref down to a child component. This is particularly useful when you need direct access to a DOM element or a class component instance which is available in a child component.

Sounds kinda similar to passing props to children right?

But ForwardRefs holds a different purpose, it allows you to pass refs through components to a child, usually to access a DOM element or a class component instance directly for imperative operations. This is not about passing down data for rendering like with props but about giving a parent component direct access to a DOM node managed by a child.

Normally, refs are not "passable" through components because they are not part of the component's props. However, React provides the React.forwardRef API to solve this problem.

Here is a code snippet which shows how you pass a ref from a parent component to a child a component

const ChildComponent = React.forwardRef((props, ref) => (
  <div ref={ref}>I'm a child</div>
));

function ParentComponent() {
  const childRef = useRef();

  useEffect(() => {
    console.log(childRef.current); // Directly access the child's div
  }, []);

  return <ChildComponent ref={childRef} />;
}
Enter fullscreen mode Exit fullscreen mode

This way my parent component gets the whole reference of the child Component’s any element.

Now I was curious, that why do I need to prefix my functional component with this ‘React.forwardRef’? I mean why can't I just pass my ref like any other prop?

Turns out if I try to pass a ref like this <ChildComponent ref={childRef} /> , and in the child component there is no prefix of React.forwardRef, it won’t work the way it was intended.

This happens because, In the Child component, we are trying to destructure ref from props,

This is problematic because ref is a reserved keyword in React. It's not passed into your component as a regular prop. React treats ref (and key) specially, and they are not part of the props object passed to your component. Thus, attempting to destructure ref from props like this won't work because ref will not exist on the props object.

Now it may sound like bypassing React’s conventions but I tried to find a work around where I can pass the ref from parent to child component without using React.forwardRef, Here is the snippet for that -

function ChildComp({Tref}) {
  return (
    <div ref={Tref} >App</div>
  )
}


function App() {
  const childRef = useRef();


  useEffect(() => {
    console.log(childRef.current); // Directly access the child's div
  }, []);


  return <ChildComp Tref={childRef} />;
}

Enter fullscreen mode Exit fullscreen mode

Because ref is a reserved keyword I just added a ‘T’ in the starting and now it works like any other prop, I can pass it to the child component and it returns me the reference of the child component’s element to the parent component.

BUT

This method was just because I was curious to know what I can do with this, in a production environment, practices or experiments like these should not be done, and there are many reasons for that.

  1. DX (Developer Experience) - When other developers will read your code if they’ll see the prefix React.forwardRef, they’ll know that you are passing refs from parent component to child component at the first glance, which increases code maintainability

  2. Future React Compatibility - React's development team continually improves the framework. While your workaround might work now, there's no guarantee it will be compatible with future versions of React. Following the recommended patterns ensures better forward compatibility.

  3. Static Typing - If you're using TypeScript or PropTypes for type checking, React.forwardRef integrates smoothly, allowing you to specify types for both props and refs. Custom patterns might require additional effort to type check correctly.

  4. DevTools Inspection - Components wrapped in React.forwardRef are better supported by React DevTools. The forwarding of refs is a recognized pattern, and such components can be inspected more intuitively in the DevTools, improving debuggability.

Although none of these points, point to a breaking problem where the code won't even run the way it was intended, it is still recommended to use React.forwardRef, the way it is intended to use.

Well that was all regarding refs from my side, here is a takeaway from this blog

  1. Understanding Refs - Refs provide a way to access DOM nodes directly within React components, bridging the gap between React's virtual DOM and the actual DOM.

  2. useRef vs. useState - We discussed why useRef is the preferred method for referencing DOM nodes without triggering unnecessary re-renders, in contrast to useState, which is designed for data that impacts the UI and requires re-rendering.

  3. Practical Applications - Through examples, we’ve seen how refs can be used for focusing elements, measuring them, and more complex tasks like forwarding refs to access DOM elements in child components.

  4. Best Practices: While refs are powerful, we emphasized their proper use and the importance of not overusing them, adhering to React’s design principles for clean and maintainable code.

Remember, while ref is a great tool in the React, it comes with the responsibility of using it judiciously to enhance, rather than complicate, your components’ interactions with the DOM.

Well, that’s all the doubts and insights I had in mind regarding Refs,
Have you encountered situations where refs were the hero of your project? Or perhaps a challenge that seemed tailor-made for a ref solution but ended up being solved differently? Share your experiences, tips, or questions in the comments below.

Additionally, if you’ve found creative uses for refs or have insights on pitfalls to avoid, let’s hear about them! Engaging with each other’s stories and strategies can lead to a deeper understanding of React’s capabilities.

Thanks for reading this far 😁

. . . . . . . . . . . . . . .