Skip to content

Commit 6bc71ab

Browse files
committed
Add refs / forwardRefs / useRef docs
1 parent b661756 commit 6bc71ab

File tree

4 files changed

+590
-0
lines changed

4 files changed

+590
-0
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
---
2+
title: Forwarding Refs
3+
description: "Forwarding Ref values in ReScript and React"
4+
canonical: "/docs/react/latest/forwarding-refs"
5+
category: "Guides"
6+
---
7+
8+
# Forwarding Refs
9+
10+
<Intro>
11+
12+
Ref forwarding is a technique for automatically passing a [React.ref](./refs-and-the-dom) 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.
13+
14+
</Intro>
15+
16+
## Forwarding Refs to DOM Components
17+
18+
Consider a FancyButton component that renders the native button DOM element:
19+
20+
```res
21+
// FancyButton.res
22+
23+
@react.component
24+
let make = (~children) => {
25+
<button className="FancyButton">
26+
children
27+
</button>
28+
}
29+
```
30+
31+
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.
32+
33+
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.
34+
35+
**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.**
36+
37+
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:
38+
39+
40+
<CodeTab labels={["ReScript", "JS Output"]}>
41+
42+
```res
43+
// App.res
44+
45+
module FancyInput = {
46+
@react.component
47+
let make = React.forwardRef((~className=?, ~children, ref_) =>
48+
<div>
49+
<input
50+
type_="text"
51+
?className
52+
ref=?{Js.Nullable.toOption(ref_)->Belt.Option.map(
53+
ReactDOMRe.Ref.domRef,
54+
)}
55+
/>
56+
children
57+
</div>
58+
)
59+
}
60+
61+
@bs.send external focus: Dom.element => unit = "focus"
62+
63+
@react.component
64+
let make = () => {
65+
let input = React.useRef(Js.Nullable.null)
66+
67+
let focusInput = () =>
68+
input.current
69+
->Js.Nullable.toOption
70+
->Belt.Option.forEach(input => input->focus)
71+
72+
let onClick = _ => focusInput()
73+
74+
<div>
75+
<FancyInput className="fancy" ref=input>
76+
<button onClick> {React.string("Click to focus")} </button>
77+
</FancyInput>
78+
</div>
79+
}
80+
```
81+
82+
```js
83+
var React = require("react");
84+
var Belt_Option = require("./stdlib/belt_Option.js");
85+
var Caml_option = require("./stdlib/caml_option.js");
86+
87+
var make = React.forwardRef(function (Props, ref_) {
88+
var className = Props.className;
89+
var children = Props.children;
90+
var tmp = {
91+
type: "text"
92+
};
93+
var tmp$1 = Belt_Option.map((ref_ == null) ? undefined : Caml_option.some(ref_), (function (prim) {
94+
return prim;
95+
}));
96+
if (tmp$1 !== undefined) {
97+
tmp.ref = Caml_option.valFromOption(tmp$1);
98+
}
99+
if (className !== undefined) {
100+
tmp.className = Caml_option.valFromOption(className);
101+
}
102+
return React.createElement("div", undefined, React.createElement("input", tmp), children);
103+
});
104+
105+
var FancyInput = {
106+
make: make
107+
};
108+
109+
function App(Props) {
110+
var input = React.useRef(null);
111+
var onClick = function (param) {
112+
return Belt_Option.forEach(Caml_option.nullable_to_opt(input.current), (function (input) {
113+
input.focus();
114+
115+
}));
116+
};
117+
return React.createElement("div", undefined, React.createElement(make, {
118+
className: "fancy",
119+
children: React.createElement("button", {
120+
onClick: onClick
121+
}, "Click to focus"),
122+
ref: input
123+
}));
124+
}
125+
```
126+
127+
</CodeTab>
128+
129+
**Note:** Our `@react.component` decorator transforms our labeled argument props within our `React.forwardRef` function in the same manner as our classic `make` function.
130+
131+
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.
132+
133+
## Note for Component Library Maintainers
134+
135+
136+
**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.

pages/docs/react/latest/hooks-ref.mdx

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
---
2+
title: useRef Hook
3+
description: "Details about the useRef React hook in ReScript"
4+
canonical: "/docs/react/latest/hooks-ref"
5+
category: "Hooks & State Management"
6+
---
7+
8+
# useRef
9+
10+
<Intro>
11+
12+
The `useRef` hooks creates and manages mutable containers inside your React component.
13+
14+
</Intro>
15+
16+
## Usage
17+
18+
<CodeTab labels={["ReScript", "JS Output"]}>
19+
20+
```res
21+
let refContainer = React.useRef(initialValue);
22+
```
23+
24+
```js
25+
var button = React.useRef(null);
26+
React.useRef(0);
27+
```
28+
29+
</CodeTab>
30+
31+
`React.useRef` returns a mutable ref object whose `.current` record field is initialized to the passed argument (`initialValue`). The returned object will persist for the full lifetime of the component.
32+
33+
Essentially, a `React.ref` is like a "box" that can hold a mutable value in its `.current` record field.
34+
35+
You might be familiar with refs primarily as a way to access the DOM. If you pass a ref object to React with `<div ref={ReactDOM.Ref.domRef(myRef)} />`, React will set its `.current` property to the corresponding DOM node whenever that node changes.
36+
37+
However, `useRef()` is useful for more than the ref attribute. It's handy for keeping any mutable value around similar to how you’d use instance fields in classes.
38+
39+
This works because `useRef()` creates a plain JavaScript object. The only difference between `useRef()` and creating a `{current: ...}` object yourself is that useRef will give you the same ref object on every render.
40+
41+
42+
Keep in mind that `useRef` doesn’t notify you when its content changes. Mutating the `.current` record field doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a [callback ref](./refs-and-the-dom#callback-refs) instead.
43+
44+
More infos on direct DOM manipulation can be found in the [Refs and the DOM](./refs-and-the-dom) section.
45+
46+
## Examples
47+
48+
### Managing Focus for a Text Input
49+
50+
51+
<CodeTab labels={["ReScript", "JS Output"]}>
52+
53+
```res
54+
// TextInputWithFocusButton.re
55+
56+
@bs.send external focus: Dom.element => unit = "focus"
57+
58+
@react.component
59+
let make = () => {
60+
let inputEl = React.useRef(Js.Nullable.null)
61+
62+
let onClick = _ => {
63+
inputEl.current
64+
->Js.Nullable.toOption
65+
->Belt.Option.forEach(input => input->focus)
66+
}
67+
68+
<>
69+
<input ref={ReactDOM.Ref.domRef(inputEl)} type_="text" />
70+
<button onClick> {React.string("Focus the input")} </button>
71+
</>
72+
}
73+
```
74+
75+
```js
76+
function TextInputWithFocusButton(Props) {
77+
var inputEl = React.useRef(null);
78+
var onClick = function (param) {
79+
return Belt_Option.forEach(Caml_option.nullable_to_opt(inputEl.current), (function (input) {
80+
input.focus();
81+
82+
}));
83+
};
84+
return React.createElement(React.Fragment, undefined, React.createElement("input", {
85+
ref: inputEl,
86+
type: "text"
87+
}), React.createElement("button", {
88+
onClick: onClick
89+
}, "Focus the input"));
90+
}
91+
```
92+
93+
</CodeTab>
94+
95+
### Using a Callback Ref
96+
97+
Reusing the example from our [Refs and the DOM](./refs-and-the-dom#callback-refs) section:
98+
99+
<CodeTab labels={["ReScript", "JS Output"]}>
100+
101+
```res
102+
// CustomTextInput.re
103+
104+
@bs.send external focus: Dom.element => unit = "focus"
105+
106+
@react.component
107+
let make = () => {
108+
let textInput = React.useRef(Js.Nullable.null)
109+
let setTextInputRef = element => {
110+
textInput.current = element;
111+
}
112+
113+
let focusTextInput = _ => {
114+
textInput.current
115+
->Js.Nullable.toOption
116+
->Belt.Option.forEach(input => input->focus)
117+
}
118+
119+
<div>
120+
<input type_="text" ref={ReactDOM.Ref.callbackDomRef(setTextInputRef)} />
121+
<input
122+
type_="button" value="Focus the text input" onClick={focusTextInput}
123+
/>
124+
</div>
125+
}
126+
```
127+
128+
```js
129+
function CustomTextInput(Props) {
130+
var textInput = React.useRef(null);
131+
var setTextInputRef = function (element) {
132+
textInput.current = element;
133+
134+
};
135+
var focusTextInput = function (param) {
136+
return Belt_Option.forEach(Caml_option.nullable_to_opt(textInput.current), (function (input) {
137+
input.focus();
138+
139+
}));
140+
};
141+
return React.createElement("div", undefined, React.createElement("input", {
142+
ref: setTextInputRef,
143+
type: "text"
144+
}), React.createElement("input", {
145+
type: "button",
146+
value: "Focus the text input",
147+
onClick: focusTextInput
148+
}));
149+
}
150+
```
151+
152+
</CodeTab>

0 commit comments

Comments
 (0)