A better way of using refs in React
Although the common way for a React component to interact with a child is by re-rendering it with new props, sometimes it could be necessary to imperatively modify it; for example to access custom components API, manipulate the DOM to do some animation, handle uncontrolled components, integrate non-react stuff, etc.
React provides a way to do this imperative stuff through the especial attribute ref
:
At mount time, the ref
callback will receive the DOM element (in case you want to store an HTML element) or the mounted instance of the component (in case you want to store a custom component declared as a class).
The broken ref
So far, this approach is enough to deal with simple React components but, what would happen if the <CustomComponent />
needed to be wrapped?
ref
is a special attribute so it won’t be passed through to the wrapped component as the rest of the props. We just lost the ref
to the <CustomComponent />
so we cannot access its API anymore.
The most common solutions to this problem are based on adapting the wrappers to ensure the access to the component’s API:
- Transparent
ref
delegation: The wrapper proxifies the component’s API exposing methods that just delegate the implementation to the wrapped component.
- Explicit
ref
delegation: The wrapper component is in charge of setting theref
attribute to the inner component with the callback received in a knownprop
, in the following examplecomponentRef
.
Although this solution requires much less boilerplate code than the previous one, it still requires the adaptation of its closest wrapper. The wrapper adaptation involves two main problems:
- Code maintenance: Both component and wrapper are coupled, which means that if a new wrapper is added, the old closest wrapper and the new one must be adapted.
- External libraries: If the closest wrapper of the component belongs to an external library, we cannot adapt it without keeping a local modified copy of the library.
Implicit ref delegation
This solution comes from trying to get the wrappers to be totally agnostic about the ref delegation, on the basis of the previous solution.
The implicit ref
delegation consists in emulating the React refs
mechanism through a new special prop (innerRef
in the example bellow). This way, the callback can flow transparently through the wrappers leaving the responsibility to finally set the ref
to the component itself:
This way, we only have to adapt the component itself to allow the use of its API methods. No boilerplate, easy maintenance and no problem dealing with external libraries (as long as they let the props
pass to the wrapped component 😅).