No Code, No Bug

What is hidden behind React's refs ?


There was a time I used to struggle when manipulating the refs in my react components.

Sometimes they returned undefined, sometimes they didn't return the DOM element I was looking for. To be honest, I didn't know anything about what I was doing. I was a total noob. I used to try any combination possible until my ref looked like what I wanted to get in the beginning.

And even then, I had no clue on how to use the ref properly. One day, I decided to dig into the official documentation and React's code to unravel this mystery once and for all.

You probably already are familiar with refs handling in React. But if you are not, I have good hopes this article will help you a lot.

Refs and React

First things first. What is a ref in React ?

It is very important to understand that a ref can be two distinct entities:

  • a React component
  • a DOM element (like a <div>)

In both cases, the ref is a node and has a type - it is an instance of a JavaScript Class with methods & properties. We will go through the details in the next section.

refs allow you to manipulate an element outside of React's lifecycle flow, and to access some of its properties & methods in an imperative way.

Note: While it is discouraged to overuse the refs, it can sometimes provide an efficient solution to a complex UI problem.

When to use refs ?

Why should we use refs anyway ? Isn't the React Top-Level API enough ? Well, in some cases it is not.

Like the React's docs state it, refs can be used for managing the focus on an element, do operations with text selection, trigger imperative animations and sometimes integrate third-parties to your components.

Still a bit unclear? No problem. Let's go through a perfect use-case (according to me) of the refs.

Imagine you are building a credit-card payment form. You will need an <input> field for the credit card's number. Of course, you want to provide a good UX to your users, so you want to shape the value of the card with the following pattern: XXXX XXXX XXXX XXXX.

See the spaces between the 4-digits groups ?
See the spaces between the 4-digits groups ?

When the user enters the 4 first digits of the credit card, you want the cursor to jump to the 5th position and add a space after the last digit: 4242 |.

Bad news: there is no way to do this without hacks or using the React API. Good News: refs will make you able to achieve that.

You can indeed move the cursor of the <input> to the position of your choice using the native JavaScript API. The two methods that will move that cursor for you are setSelectionRange() & focus(). Well, the ref you attached to the <input> contains those two methods!

Here is a live demo of this component. Now let's go through the technical details.

The hidden truth

I swear to tell the truth, and only the truth.

source: i.imgflip.com
source: i.imgflip.com

Now we know that refs are not just random objects, let's dive into the depth of their implementation.

When a ref is attached to a DOM element

Before we go through examples, let's define clearly what is the instanced class of a ref that was attached to a DOM element.

All DOM elements follow this architecture:

source: developer.mozilla.org
source: developer.mozilla.org

A DOM element:

  • implements the interface of its tag. For example, like in the above screenshot, a <div> implements the interface HTMLDivElement
  • implements the interface HTMLElement
  • is an instance of the class Element

This simply means that the ref you attach to a DOM element like a <div> will be an object that inherits all the properties & methods the class and the interfaces offer. That said, you will be able, for example, to access the different properties of your DOM element's width from the ref (like clientWidth, offsetWidth and so on).

Also, React will add some hidden properties & methods to the ref object. But these are treated like private stuff - you don't need to use those or even know their existence.

Depending on the browser (for example some properties & methods are not available in our worst nightmare - yes, I'm talking about IE 😵), your ref will look a bit like that:

// the content of a ref attached to a <div>

{
  "__reactEventHandlers$q0kumw04tr": Object { children: (4) […], … },
  "__reactInternalInstance$q0kumw04tr": Object { tag: 5, elementType: "div", type: "div", … },
  accessKey: "",
  accessKeyLabel: "",
  align: "",
  assignedSlot: null,
  attributes: NamedNodeMap [],
  childElementCount: 4,
  childNodes: NodeList(4) [ div, div, h1, … ],
  children: HTMLCollection(4) [ div, div, h1, … ],
  classList: DOMTokenList [],
  className: "",
  clientHeight: 160,
  clientLeft: 0,
  clientTop: 0,
  clientWidth: 77,
  ...
  ...
  and many more stuff!
}

When a ref is attached to a React component

There might be some edge cases (very rare) where you might want to attach a ref to a React component.

refs cannot be attached to functional components as is - it requires to use the React.forwardRef HOC we'll see in details later.

Functional components, or stateless components, cannot have a ref attached because there is no state to attach the ref to. It's as simple as that. Like said previously, the hooks solve this problem since react@^16.8.0.

However, you can attach a ref to a Class Component.

In this case, your ref:

That said, you will have access to the state, the props and the context of the component your ref was attached to.

Your ref will look like that:

// the content of the ref you attached to your React component

{
  _reactInternalFiber: Object { tag: 1, key: null, index: 0, … },
  _reactInternalInstance: Object { … },
  context: Object {  },
  props: Object { … },
  refs: Object {  },
  state: null,
  updater: Object { isMounted: isMounted(component), enqueueSetState: enqueueSetState(...),
  ...
  ...
  And some more stuff!
}

Just like for the DOM element, React will add some private properties & methods in the ref - that's for the internal usage of React. You will never need to access those.

source: roioverload.com
source: roioverload.com

Alright, enough with the theory. Now you know exactly what you are handling, let's see how to use React's refs API! 🤘

Creating the ref

There are actually two methods in order to create a ref. In both cases, the method returns an object that will always have the following structure:

// content of your ref
{
  current: HTMLElement | React.Component | undefined
}

So you will be able to access your targeted node (React or DOM) via ref.current

Creating a ref couldn't be more straight-forward - if you are in a class component, you can use the React.createRef method.

A good practice is to create the ref in your constructor:

class YourComponent extends React.Component {
  constructor(props) {
    super(props)
    this.ref = React.createRef()
  }
}

Inside a functional component, you can use the React.useRef hook. Here's how to use it:

const YourComponent = () => {
  const ref = React.useRef()
}

Now your ref is initialized and ready to be attached to a React class component or a DOM element. Let's see how to use it.

Using the ref

Once initialized, your ref needs to be attached to something. If you want to attach it to a React class component:

class DummyComponent extends React.Component {
  render() {
    return <p>I am a dummy component</p>
  }
}

class YourComponent extends React.Component {
  constructor(props) {
    super(props)
    this.ref = React.createRef() // mutable object
  }

  componentDidMount() {
    // reactNode: DummyComponent
    const reactNode = this.ref.current
  }

  render() {
    return <DummyComponent ref={this.ref} />
  }
}

If you want to attach it to a DOM element:

// You could use a class component as well.

const YourComponent = () => {
  const ref = React.useRef() // mutable object

  React.useEffect(() => {
    // divNode: HTMLDivElement
    const divNode = ref.current
  }, [])

  return <div ref={ref}>I am a div</div>
}

Forwarding the ref with forwardRef

Finally getting there. If you read the previous part (which I trust you to have done 🙂) you may wonder what is React.forwardRef there for ? After all, it seems at first glance that we got all we need already.

Well, not quite so. We are missing one problem: what if you want the ref you attached to a React component to go to the first DOM element it renders ? You cannot. As seen previously, the ref you attach to a React component will return the instance of this very component.

Fortunately for us, React thought about this and offers us a simple solution: React.forwardRef.

Note: It is an HOC. So, in brief, it's a function that takes a React component as parameter, and also returns a React component.

It looks like:

// returns a React component
React.forwardRef((props, ref) => <YourComponent forwardedRef={ref} {...props} />)

Its sole purpose is to inform your React component to pass the ref you attached to it down to its props instead of itself. So you can pass it to the DOM element of your chosing in its render.

Here's how to use it:

const DummyComponent = React.forwardRef((props, ref) => {
  const { children } = props
  return <div ref={ref}>{children}</div>
})

const YourComponent = () => {
  const ref = React.useRef()

  React.useEffect(() => {
    // divNode: HTMLDivElement
    const divNode = ref.current
  }, [])

  return <DummyComponent ref={ref}>I am a dummy div</DummyComponent>
}

In the above example, divNode is of type HTMLDivElement and it contains the "I am a dummy div" text.

Bonus: sharing forwarded and local refs

There are cases where you might need, at the same time, to have a local ref and also to forward this local ref to the component's parent.

This can happen for example when you are implementating an input-like component, and need to access the HTMLInputElement's ref locally but also to forward it to the parent.

Here's the secret: React.useImperativeHandle! 🤐

This method will enable you to expose the local ref to the parent, while keeping the same source of truth.

You can use it this way:

React.useImperativeHandle(forwardedRef, () => localRef.current)

Here's a more tangible example:

const MyInput = React.forwardRef((props, ref) => {
  const localRef = React.useRef()
  React.useImperativeHandle(ref, () => localRef.current)
  return <input ref={localRef} />
})

const YourComponent = () => {
  const ref = React.useRef()

  React.useEffect(() => {
    const inputNode = ref.current
  }, [])

  return <MyInput ref={ref} />
}

Now you are able to handle the input's ref in both the parent and child components!

Note: inputNode and localRef share the same reference.

Conclusion

I really hope I made the whole ref party much more clear to you now, and that you can't wait to try out the bonus example! 😉

refs system can be quite difficult to understand at first, but once you know what is the type of the elements your are handling and how the refs work behind the scene, you realize that it's quite fun in the end.

While React offers us these tools, they strongly discourage us from over-using them because their imperative pattern goes against the asynchronous pattern of React's components lifecycle.

However there are some UI problems that cannot be solved simply: refs can come handy in those situations.

If you have a question, a remark, or want to bark how crap you found this article, don't hesitate to dm me on Twitter! You will find the link below.

Thanks for reading! 😃

What you can read for further reading:

← Back to main page

Alexandre Le Lain

Alexandre Le Lain ‹@a_lelain

Software Engineer at HelloWork. I write code to fix my code.

You can check out my Github anytime.