Refs in React: from access to DOM to imperative API
Tags: webdev, javascript, react, tutorial
Published: 2023-04-04 07:15:00


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9gtuhy112t1xns9rmlj2.png)

Originally published at [https://www.developerway.com](https://www.developerway.com). The website has more articles like this 😉

---

One of the many beautiful things about React is that it abstracts away the complexity of dealing with real DOM. Now, instead of manually querying elements, scratching our heads over how to add classes to those elements, or struggling with browser inconsistencies, we can just write components and focus on user experience. There are, however, still cases (very few though!) when we need to get access to the actual DOM.

And when it comes to actual DOM, the most important thing to understand and learn how to use properly is Ref and everything surrounding Ref. So today, let’s take a look at why we would want to get access to DOM in the first place, how Ref can help with that, what are `useRef`, `forwardRef` and `useImperativeHandle`, and how to use them properly. Also, let’s investigate how to _avoid_ using `forwardRef` and `useImperativeHandle` while still having what they give us. If you ever tried to figure out how those work, you’ll understand why we’d want that 😅

And as a bonus, we’ll learn how to implement imperative APIs in React!

This article is also available [as a YouTube video](https://youtu.be/H9KhRiO1UeU): it has fewer details but nice animations instead. Sometimes it's easier to understand a concept from a 3-second animation than from two paragraphs of text.


<h2 id="part1"><a href="#part1">DOM access in React with useRef</a></h2>


Let’s say I want to implement a sign-up form for a conference I’m organizing. I want people to give me their name, email and Twitter handle before I can send them the details. “Name” and “email” fields I want to make mandatory. But I don’t want to show some annoying red borders around those inputs when people try to submit them empty, I want the form to be cool. So instead, I want to focus the empty field and shake it a little to attract attention, just for the fun of it.

Now, React gives us a lot, but it doesn’t give us _everything_. Things like “focus an element manually” is not part of the package. For that, we need to dust off our rusty native javascript API skills. And for that, we need access to the actual DOM element.

In the no-React world, we’d do something like this:

```tsx
const element = document.getElementById("bla");
```

After then we can focus it:

```tsx
element.focus();
```

Or scroll to it:

```tsx
element.scrollIntoView();
```

Or anything else our heart desires. Some typical use cases for using native DOM API in React world would include:

* manually **focusing an element** after it’s rendered, like an input field in a form
* detecting **a click outside of a component** when showing popup-like elements
* manually **scrolling to an element** after it appears on the screen
* **calculating sizes and boundaries** of components on the screen to position something like a tooltip correctly

And although, technically, nothing is stopping us from doing `getElementById` even today, React gives us a slightly more powerful way to access that element that doesn’t require us to spread ids everywhere or be aware of the underlying DOM structure: [refs](https://react.dev/reference/react/useRef#manipulating-the-dom-with-a-ref).

Ref is just a mutable object, the reference to which React preserves between re-renders. It doesn’t trigger re-render, so it’s not a replacement to state in any way, don’t try to use it for it. More details on the difference between those two [are in the docs](https://react.dev/learn/referencing-values-with-refs#differences-between-refs-and-state).

It’s created with `useRef` hook:

```tsx
const Component = () => {
  // create a ref with default value null
  const ref = useRef(null);

  return ...
}
```

And the value stored in Ref will be available in “current” (and only) property of it. And we can actually store anything in it! We can, for example, store an object with some values coming from state:

```tsx
const Component = () => {
  const ref = useRef(null);

  useEffect(() => {
    // re-write ref's default value with new object
    ref.current = {
      someFunc: () => {...},
      someValue: stateValue,
    }
  }, [stateValue])


  return ...
}
```

Or, more importantly for our use case, we can assign this Ref to any DOM element and some of the React components:

```tsx
const Component = () => {
  const ref = useRef(null);

  // assing ref to an input element
  return <input ref={ref} />
}
```

Now, if I log `ref.current` in `useEffect` (it’s only available after a component is rendered), I’ll see _exactly_ the same element that I would get if I try to do `getElementById` on that input:

```tsx
const Component = () => {
  const ref = useRef(null);

  useEffect(() => {
    // this will be a reference to input DOM element!
    // exactly the same as if I did getElementById for it
    console.log(ref.current);
  });

  return <input ref={ref} />
}
```

And now, if I was implementing my sign-up form as one giant component, I could do something like this:

```tsx
const Form = () => {
  const [name, setName] = useState('');
  const inputRef = useRef(null);

  const onSubmitClick = () => {
    if (!name) {
      // focus the input field if someone tries to submit empty name
      ref.current.focus();
    } else {
      // submit the data here!
    }
  }

  return <>
    ...
    <input onChange={(e) => setName(e.target.value)} ref={ref} />
    <button onClick={onSubmitClick}>Submit the form!</button>
  </>
}
```

Store the values from inputs in state, create refs for all inputs, and when “submit” button is clicked, I would check whether the values are not empty, and if they are - focus the needed input.

Check out this form implementation in this codesandbox:

{% embed https://codesandbox.io/embed/giant-form-ref-example-23z9vf?file=/src/App.tsx?codemirror=1&hidedevtools=1&view=preview %}


<h2 id="part2"><a href="#part2">Passing ref from parent to child as a prop</a></h2>

Only in real life, I wouldn’t do one giant component with everything of course. More likely than not, I would want to extract that input into its own component: so that it can be reused across multiple forms, and can encapsulate and control its own styles, maybe even have some additional features like having a label on the top or an icon on the right.

```tsx
const InputField = ({ onChange, label }) => {
  return <>
    {label}<br />
    <input type="text" onChange={(e) => onChange(e.target.value)} />
  </>
}
```

But error handling and submitting functionality still going to be in the `Form`, not input!

```tsx
const Form = () => {
  const [name, setName] = useState('');

  const onSubmitClick = () => {
    if (!name) {
      // deal with empty name
    } else {
      // submit the data here!
    }
  }

  return <>
    ...
    <InputField label="name" onChange={setName} />
    <button onClick={onSubmitClick}>Submit the form!</button>
  </>
}
```

How can I tell input to “focus itself” from the Form component? The “normal” way to control data and behaviour in React is to pass props to components and listen to callbacks. I could try to pass the prop “focusItself” to `InputField` that I would switch from `false` to `true`, but that would work only once.

```tsx
// don't do this! just to demonstate how it could work in theory
const InputField = ({ onChange, focusItself }) => {
  const inputRef = useRef(null);

  useEffect(() => {
    if (focusItself) {
      // focus input if the focusItself prop changes
      // will work only once, when false changes to true
      ref.current.focus();
    }
  }, [focusItself])

  // the rest is the same here
}
```

I could try to add some “onBlur” callback and reset that `focusItself` prop to `false` when input loses focus, or play around with random values instead of boolean, or come up with some other creative solution.

Likely, there is another way. Instead of fiddling around with props, we can just create Ref in one component (`Form`), pass it down to another component (`InputField`), and attach it to the underlying DOM element there. Ref is just a mutable object, after all.

`Form` would then create the Ref as normal:

```tsx
const Form = () => {
  // create the Ref in Form component
  const inputRef = useRef(null);

  ...
}
```

And `InputField` component will have a prop that accepts the ref, and will have `input` field that accepts the ref, as usual. Only Ref, instead of being created in `InputField`, will be coming from props there:

```tsx
const InputField = ({ inputRef }) => {
  // the rest of the code is the same

  // pass ref from prop to the internal input component
  return <input ref={inputRef} ... />
}
```

Ref is a _mutable_ object, was designed that way. When we pass it to an element, React underneath just mutates it. And the object that is going to be mutated is declared in the `Form` component. So as soon as `InputField` is rendered, Ref object will mutate, and our `Form` will have access to the `input` DOM element in `inputRef.current`:

```tsx
const Form = () => {
  // create the Ref in Form component
  const inputRef = useRef(null);

  useEffect(() => {
    // the "input" element, that is rendered inside InputField, will be here
    console.log(inputRef.current);
  }, []);

  return (
    <>
      {/* Pass ref as prop to the input field component */}
      <InputField inputRef={inputRef} />
    </>
  )
}
```

or in our submit callback we can call the inputRef.current.focus(), exactly the same code as before.

Check out the example here:

{% embed https://codesandbox.io/embed/example2-pass-ref-as-prop-5i03u9?file=/src/App.tsx?codemirror=1&hidedevtools=1&view=preview %}

<h2 id="part3"><a href="#part3">Passing ref from parent to child with forwardRef</a></h2>

In case you’re wondering why I named the prop `inputRef`, rather than just `ref`: it’s actually not that simple. `ref` is not an actual prop, it's kinda a “reserved” name. In the old days, when we were still writing class components, if we passed ref to a class component, this component’s instance would be the `.current` value of that Ref.

But functional components don’t have instances. So instead, we just get a warning in console “Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?”

```tsx
const Form = () => {
  const inputRef = useRef(null);

  // if we just do this, we'll get a warning in console
  return <InputField ref={inputRef} />
}
```

In order for this to work, we need to signal to React that this `ref` is actually intentional, we want to do stuff with it. We can do it with the help of `forwardRef` function: it accepts our component and _injects_ the ref from the `ref` attribute as a second argument of the component’s function. Right after the props.

```tsx
// normally, we'd have only props there
// but we wrapped the component's function with forwardRef
// which injects the second argument - ref
// if it's passed to this component by its consumer
const InputField = forwardRef((props, ref) => {
  // the rest of the code is the same

  return <input ref={ref} />
})
```

We could even split the above code into two variables for better readability:

```tsx
const InputFieldWithRef = (props, ref) => {
  // the rest is the same
}

// this one will be used by the form
export const InputField = forwardRef(InputFieldWithRef);
```

And now the `Form` can just pass ref to the `InputField` component as it was a regular DOM element:

```tsx
return <InputField ref={inputRef} />
```

Whether you should use `forwardRef` or just pass ref as a prop is just a matter of personal taste: the end result is the same.

See the live example here:

{% embed https://codesandbox.io/embed/example3-pass-ref-with-forwardref-eg30h5?file=/src/App.tsx?codemirror=1&hidedevtools=1&view=preview %}


<h2 id="part4"><a href="#part4">Imperative API with useImperativeHandle</a></h2>

Okay, focusing the input from the `Form` component is sorted, kinda. But we are in no way done with our cool form. Remember, we wanted to shake the input in addition to focusing when the error happens? There is no such thing as `element.shake()` in native javascript API, so access to the DOM element won’t help here 😢

We could very easily implement it as a CSS animation though:

```tsx
const InputField = () => {
  // store whether we should shake or not in state
  const [shouldShake, setShouldShake] = useState(false);

  // just add the classname when it's time to shake it - css will handle it
  const className = shouldShake ? "shake-animation" : '';

  // when animation is done - transition state back to false, so we can start again if needed
  return <input className={className} onAnimationEnd={() => setShouldShake(false)} />
}
```

But how to trigger it? Again, the same story as before with focus - I could come up with some creative solution using props, but it would look weird and significantly over-complicate the Form. Especially considering that we're handling focus through ref, so we'd have two solutions for exactly the same problem. If only I could do something like `InputField.shake()` and `InputField.focus()` here!

And speaking of focus - why my `Form` component still has to deal with native DOM API to trigger it? Isn’t it the responsibility and the whole point of the `InputField`, to abstract away complexities like this? Why does the form even have access to the underlying DOM element - it’s basically leaking internal implementation details. The `Form` component shouldn’t care which DOM element we’re using or whether we even use DOM elements or something else at all. Separation of concerns, you know.

Looks like it’s time to implement a proper imperative API for our `InputField` component. Now, React is declarative and expects us all to write our code accordingly. But sometimes we just need a way to trigger something imperatively. Likely, React gives us an escape hatch for it: [useImperativeHandle](https://react.dev/reference/react/useImperativeHandle) hook.

This hook is slightly mind-boggling to understand, I had to read the docs twice, try it out a few times and go through its implementation in the actual React code to really get what it’s doing. But essentially, we just need two things: decide how our imperative API would look like and a Ref to attach it to. For our input, it’s simple: we just need `.focus()` and `.shake()` functions as an API, and we already know all about refs.

```tsx
// this is how our API could look like
const InputFieldAPI = {
  focus: () => {
    // do the focus here magic
  },
  shake: () => {
    // trigger shake here
  }
}
```

This `useImperativeHandle` hook just attaches this object to Ref object's "current" property, that's all. This is how it does it:

```tsx
const InputField = () => {

  useImperativeHandle(someRef, () => ({
    focus: () => {},
    shake: () => {},
  }), [])

}
```

The first argument - is our Ref, which is either created in the component itself, passed from props or through forwardRef. The second argument is a function that returns an object - this is the object that will be available as `inputRef.current`. And the third argument is the array of dependencies, same as any other React hook.

For our component, let’s pass the ref explicitly as `apiRef` prop. And the only thing that is left to do is to implement the actual API. For that we’d need another ref - this time internal to `InputField`, so that we can attach it to the `input` DOM element and trigger focus as usual:

```tsx
// pass the Ref that we'll use as our imperative API as a prop
const InputField = ({ apiRef }) => {
  // create another ref - internal to Input component
  const inputRef = useRef(null);

  // "merge" our API into the apiRef
  // the returned object will be available for use as apiRef.current
  useImperativeHandle(apiRef, () => ({
    focus: () => {
      // just trigger focus on internal ref that is attached to the DOM object
      inputRef.current.focus()
    },
    shake: () => {},
  }), [])

  return <input ref={inputRef} />
}
```

And for “shake” we’ll just trigger the state update:

```tsx
// pass the Ref that we'll use as our imperative API as a prop
const InputField = ({ apiRef }) => {
  // remember our state for shaking?
  const [shouldShake, setShouldShake] = useState(false);

  useImperativeHandle(apiRef, () => ({
    focus: () => {},
    shake: () => {
      // trigger state update here
      setShouldShake(true);
    },
  }), [])

  return ...
}
```

And boom! Our `Form` can just create a `ref`, pass it to `InputField` and will be able to do simple `inputRef.current.focus()` and `inputRef.current.shake()`, without worrying about their internal implementation!

```tsx
const Form = () => {
  const inputRef = useRef(null);
  const [name, setName] = useState('');

  const onSubmitClick = () => {
    if (!name) {
      // focus the input if the name is empty
      inputRef.current.focus();
      // and shake it off!
      inputRef.current.shake();
    } else {
      // submit the data here!
    }
  }

  return <>
    ...
    <InputField label="name" onChange={setName} apiRef={inputRef} />
    <button onClick={onSubmitClick}>Submit the form!</button>
  </>
}
```

Play around with the full working form example here:

{% embed https://codesandbox.io/embed/example4-imperative-api-with-useimperativehandle-v5hdsm?file=/src/App.tsx?codemirror=1&hidedevtools=1&view=preview %}

<h2 id="part5"><a href="#part5">Imperative API without useImperativeHandle</a></h2>

If `useImperativeHandle` hook still makes your eye twitch - don’t worry, mine twitches too! But we don’t actually have to use it to implement that functionality that we just implemented. We already know how refs work, and the fact that they are mutable. So all that we need is just to assign our API object to the `ref.current` of the needed Ref, something like this:

```tsx
const InputField = ({ apiRef }) => {
  useEffect(() => {
    apiRef.current = {
      focus: () => {},
      shake: () => {},
    }
  }, [apiRef])
}
```

This is almost exactly what `useImperativeHandle` does under the hood anyway. And it will work exactly like before.

Actually, `useLayoutEffect` might be even better here, but this is the topic for another article. For now, let's go with traditional `useEffect`.

See the final example here:

{% embed https://codesandbox.io/embed/example5-imperative-api-without-useimperativehandle-yjyxd0?file=/src/App.tsx?codemirror=1&hidedevtools=1&view=preview %}


---

Yey, a cool form with a nice shaking effect is ready, React refs are not a mystery anymore, and imperative API in React is actually a thing. How cool is that?

Just remember: Refs are an “escape hatch”, it’s not a replacement to state or normal React data flow with props and callbacks. Use them only when there is no “normal” alternative. The same with the imperative way to trigger something - more likely than not normal props/callbacks flow is what you want.

Watch the article [in YouTube format](https://youtu.be/H9KhRiO1UeU) to solidify the knowledge 😉

---

Originally published at [https://www.developerway.com](https://www.developerway.com). The website has more articles like this 😉

[Subscribe to the newsletter](https://www.developerway.com), [connect on LinkedIn](https://www.linkedin.com/in/adevnadia/) or [follow on Twitter](https://twitter.com/adevnadia) to get notified as soon as the next article comes out.