Handling external and internal refs to the same element with useImperativeHandle in React
At Vimeo we’re big fans of React, which renders our front-end components in a modular and reusable fashion.
React’s ref
attribute is used to access components’ children or DOM elements. We normally use ref
with React’s provided Higher Order Component (HOC) forwardRef
and the useRef
hook, but it can be confusing to share the ref
attribute when using both of these methods at the same time.
This post describes how to use ref
for each of these cases, and how we use the useImperativeHandle
hook to resolve the conflict that arises when trying to use them both in the same component. This post is most relevant to React users working on systems for sharing components across products.
Passing external refs with forwardRef
We often use the Higher Order Component (HOC) forwardRef
in design systems to provide consumers of components with references to the meaningful elements in them.
For example, in the following component:
We’ve decided that the input element is meaningful because we want to interact with its value from a parent component of FancyTextSubmit
. To enable consumers of FancyTextSubmit
to interact with the input element, we use forwardRef
like so:
With this HOC, adding a ref
to FancyTextSubmit
grants access to its internal input element. This way, users can easily interact with the nested input element anytime they use FancyTextSubmit
:
See this code in action on CodeSandbox.
Note that in this example we use ref
to capture the value of the input when the user clicks the submit button. Typically when dealing with an input element in React, the first approach is to update the state from the input onChange
. In that case, our code would look more like:
By using ref
, we avoid unnecessary renders in situations that don’t require the intermediate values.
Accessing internal refs with useRef
Occasionally we also need a reference to the input within the FancyTextSubmit
component itself. For instance, if we want to set the focus back to the input when a user clicks the submit button:
The problem
Our issue here becomes that we are already using the ref
property of <input>
. We need a way to supply both the inputEl
ref and the forwarded ref.
The solution
Enter useImperativeHandle
. This hook allows the sharing of the ref
between both useRef
and forwardRef
:
See CodeSandbox for this example.