Forwarding Refs
Ref forwarding is a technique for automatically passing a React.ref through a component to one of its children. This is typically not necessary for most components in the application. However, it can be useful for some kinds of components, especially in reusable component libraries. The most common scenarios are described below.
Forwarding Refs to DOM Components
Consider a FancyButton component that renders the native button DOM element:
RES// FancyButton.res
@react.component
let make = (~children) => {
<button className="FancyButton">
children
</button>
}
React components hide their implementation details, including their rendered output. Other components using FancyButton usually will not need to obtain a ref to the inner button DOM element. This is good because it prevents components from relying on each other’s DOM structure too much.
Although such encapsulation is desirable for application-level components like FeedStory
or Comment
, it can be inconvenient for highly reusable “leaf” components like FancyButton
or MyTextInput
. These components tend to be used throughout the application in a similar manner as a regular DOM button and input, and accessing their DOM nodes may be unavoidable for managing focus, selection, or animations.
Ref forwarding is an opt-in feature that lets some components take a ref they receive, and pass it further down (in other words, “forward” it) to a child.
In the example below, FancyInput
uses React.forwardRef
to obtain the ref passed to it, and then forward it to the DOM input that it renders:
// App.res
module FancyInput = {
@react.component
let make = React.forwardRef((~className=?, ~children, ref_) =>
<div>
<input
type_="text"
?className
ref=?{Js.Nullable.toOption(ref_)->Belt.Option.map(
ReactDOMRe.Ref.domRef,
)}
/>
children
</div>
)
}
@bs.send external focus: Dom.element => unit = "focus"
@react.component
let make = () => {
let input = React.useRef(Js.Nullable.null)
let focusInput = () =>
input.current
->Js.Nullable.toOption
->Belt.Option.forEach(input => input->focus)
let onClick = _ => focusInput()
<div>
<FancyInput className="fancy" ref=input>
<button onClick> {React.string("Click to focus")} </button>
</FancyInput>
</div>
}
Note: Our @react.component
decorator transforms our labeled argument props within our React.forwardRef
function in the same manner as our classic make
function.
This way, components using FancyInput
can get a ref to the underlying input
DOM node and access it if necessary—just like if they used a DOM input
directly.
Note for Component Library Maintainers
When you start using forwardRef in a component library, you should treat it as a breaking change and release a new major version of your library. This is because your library likely has an observably different behavior (such as what refs get assigned to, and what types are exported), and this can break apps and other libraries that depend on the old behavior.