mirror of
https://github.com/reactjs/react.dev.git
synced 2026-02-23 20:23:08 +00:00
Remove forwardRef references from useRef and Manipulating the DOM with Refs pages (#7364)
This commit is contained in:
@@ -343,17 +343,36 @@ Read more about [how this helps find bugs](/reference/react/StrictMode#fixing-bu
|
||||
|
||||
## Accessing another component's DOM nodes {/*accessing-another-components-dom-nodes*/}
|
||||
|
||||
When you put a ref on a built-in component that outputs a browser element like `<input />`, React will set that ref's `current` property to the corresponding DOM node (such as the actual `<input />` in the browser).
|
||||
<Pitfall>
|
||||
Refs are an escape hatch. Manually manipulating _another_ component's DOM nodes can make your code fragile.
|
||||
</Pitfall>
|
||||
|
||||
However, if you try to put a ref on **your own** component, like `<MyInput />`, by default you will get `null`. Here is an example demonstrating it. Notice how clicking the button **does not** focus the input:
|
||||
You can pass refs from parent component to child components [just like any other prop](/learn/passing-props-to-a-component).
|
||||
|
||||
```js {3-4,9}
|
||||
import { useRef } from 'react';
|
||||
|
||||
function MyInput({ ref }) {
|
||||
return <input ref={ref} />;
|
||||
}
|
||||
|
||||
function MyForm() {
|
||||
const inputRef = useRef(null);
|
||||
return <MyInput ref={inputRef} />
|
||||
}
|
||||
```
|
||||
|
||||
In the above example, a ref is created in the parent component, `MyForm`, and is passed to the child component, `MyInput`. `MyInput` then passes the ref to `<input>`. Because `<input>` is a [built-in component](/reference/react-dom/components/common) React sets the `.current` property of the ref to the `<input>` DOM element.
|
||||
|
||||
The `inputRef` created in `MyForm` now points to the `<input>` DOM element returned by `MyInput`. A click handler created in `MyForm` can access `inputRef` and call `focus()` to set the focus on `<input>`.
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
import { useRef } from 'react';
|
||||
|
||||
function MyInput(props) {
|
||||
return <input {...props} />;
|
||||
function MyInput({ ref }) {
|
||||
return <input ref={ref} />;
|
||||
}
|
||||
|
||||
export default function MyForm() {
|
||||
@@ -376,79 +395,18 @@ export default function MyForm() {
|
||||
|
||||
</Sandpack>
|
||||
|
||||
To help you notice the issue, React also prints an error to the console:
|
||||
|
||||
<ConsoleBlock level="error">
|
||||
|
||||
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
|
||||
|
||||
</ConsoleBlock>
|
||||
|
||||
This happens because by default React does not let a component access the DOM nodes of other components. Not even for its own children! This is intentional. Refs are an escape hatch that should be used sparingly. Manually manipulating _another_ component's DOM nodes makes your code even more fragile.
|
||||
|
||||
Instead, components that _want_ to expose their DOM nodes have to **opt in** to that behavior. A component can specify that it "forwards" its ref to one of its children. Here's how `MyInput` can use the `forwardRef` API:
|
||||
|
||||
```js
|
||||
const MyInput = forwardRef((props, ref) => {
|
||||
return <input {...props} ref={ref} />;
|
||||
});
|
||||
```
|
||||
|
||||
This is how it works:
|
||||
|
||||
1. `<MyInput ref={inputRef} />` tells React to put the corresponding DOM node into `inputRef.current`. However, it's up to the `MyInput` component to opt into that--by default, it doesn't.
|
||||
2. The `MyInput` component is declared using `forwardRef`. **This opts it into receiving the `inputRef` from above as the second `ref` argument** which is declared after `props`.
|
||||
3. `MyInput` itself passes the `ref` it received to the `<input>` inside of it.
|
||||
|
||||
Now clicking the button to focus the input works:
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
import { forwardRef, useRef } from 'react';
|
||||
|
||||
const MyInput = forwardRef((props, ref) => {
|
||||
return <input {...props} ref={ref} />;
|
||||
});
|
||||
|
||||
export default function Form() {
|
||||
const inputRef = useRef(null);
|
||||
|
||||
function handleClick() {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyInput ref={inputRef} />
|
||||
<button onClick={handleClick}>
|
||||
Focus the input
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</Sandpack>
|
||||
|
||||
In design systems, it is a common pattern for low-level components like buttons, inputs, and so on, to forward their refs to their DOM nodes. On the other hand, high-level components like forms, lists, or page sections usually won't expose their DOM nodes to avoid accidental dependencies on the DOM structure.
|
||||
|
||||
<DeepDive>
|
||||
|
||||
#### Exposing a subset of the API with an imperative handle {/*exposing-a-subset-of-the-api-with-an-imperative-handle*/}
|
||||
|
||||
In the above example, `MyInput` exposes the original DOM input element. This lets the parent component call `focus()` on it. However, this also lets the parent component do something else--for example, change its CSS styles. In uncommon cases, you may want to restrict the exposed functionality. You can do that with `useImperativeHandle`:
|
||||
In the above example, the ref passed to `MyInput` is passed on to the original DOM input element. This lets the parent component call `focus()` on it. However, this also lets the parent component do something else--for example, change its CSS styles. In uncommon cases, you may want to restrict the exposed functionality. You can do that with [`useImperativeHandle`](/reference/react/useImperativeHandle):
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
import {
|
||||
forwardRef,
|
||||
useRef,
|
||||
useImperativeHandle
|
||||
} from 'react';
|
||||
import { useRef, useImperativeHandle } from "react";
|
||||
|
||||
const MyInput = forwardRef((props, ref) => {
|
||||
function MyInput({ ref }) {
|
||||
const realInputRef = useRef(null);
|
||||
useImperativeHandle(ref, () => ({
|
||||
// Only expose focus and nothing else
|
||||
@@ -456,8 +414,8 @@ const MyInput = forwardRef((props, ref) => {
|
||||
realInputRef.current.focus();
|
||||
},
|
||||
}));
|
||||
return <input {...props} ref={realInputRef} />;
|
||||
});
|
||||
return <input ref={realInputRef} />;
|
||||
};
|
||||
|
||||
export default function Form() {
|
||||
const inputRef = useRef(null);
|
||||
@@ -469,9 +427,7 @@ export default function Form() {
|
||||
return (
|
||||
<>
|
||||
<MyInput ref={inputRef} />
|
||||
<button onClick={handleClick}>
|
||||
Focus the input
|
||||
</button>
|
||||
<button onClick={handleClick}>Focus the input</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -479,7 +435,7 @@ export default function Form() {
|
||||
|
||||
</Sandpack>
|
||||
|
||||
Here, `realInputRef` inside `MyInput` holds the actual input DOM node. However, `useImperativeHandle` instructs React to provide your own special object as the value of a ref to the parent component. So `inputRef.current` inside the `Form` component will only have the `focus` method. In this case, the ref "handle" is not the DOM node, but the custom object you create inside `useImperativeHandle` call.
|
||||
Here, `realInputRef` inside `MyInput` holds the actual input DOM node. However, [`useImperativeHandle`](/reference/react/useImperativeHandle) instructs React to provide your own special object as the value of a ref to the parent component. So `inputRef.current` inside the `Form` component will only have the `focus` method. In this case, the ref "handle" is not the DOM node, but the custom object you create inside [`useImperativeHandle`](/reference/react/useImperativeHandle) call.
|
||||
|
||||
</DeepDive>
|
||||
|
||||
@@ -591,7 +547,7 @@ export default function TodoList() {
|
||||
const newTodo = { id: nextId++, text: text };
|
||||
flushSync(() => {
|
||||
setText('');
|
||||
setTodos([ ...todos, newTodo]);
|
||||
setTodos([ ...todos, newTodo]);
|
||||
});
|
||||
listRef.current.lastChild.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
|
||||
@@ -448,16 +448,16 @@ button { display: block; margin-bottom: 20px; }
|
||||
|
||||
#### Exposing a ref to your own component {/*exposing-a-ref-to-your-own-component*/}
|
||||
|
||||
Sometimes, you may want to let the parent component manipulate the DOM inside of your component. For example, maybe you're writing a `MyInput` component, but you want the parent to be able to focus the input (which the parent has no access to). You can use a combination of `useRef` to hold the input and [`forwardRef`](/reference/react/forwardRef) to expose it to the parent component. Read a [detailed walkthrough](/learn/manipulating-the-dom-with-refs#accessing-another-components-dom-nodes) here.
|
||||
Sometimes, you may want to let the parent component manipulate the DOM inside of your component. For example, maybe you're writing a `MyInput` component, but you want the parent to be able to focus the input (which the parent has no access to). You can create a `ref` in the parent and pass the `ref` as prop to the child component. Read a [detailed walkthrough](/learn/manipulating-the-dom-with-refs#accessing-another-components-dom-nodes) here.
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
import { forwardRef, useRef } from 'react';
|
||||
import { useRef } from 'react';
|
||||
|
||||
const MyInput = forwardRef((props, ref) => {
|
||||
return <input {...props} ref={ref} />;
|
||||
});
|
||||
function MyInput({ ref }) {
|
||||
return <input ref={ref} />;
|
||||
};
|
||||
|
||||
export default function Form() {
|
||||
const inputRef = useRef(null);
|
||||
@@ -554,7 +554,7 @@ You might get an error in the console:
|
||||
|
||||
<ConsoleBlock level="error">
|
||||
|
||||
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
|
||||
TypeError: Cannot read properties of null
|
||||
|
||||
</ConsoleBlock>
|
||||
|
||||
@@ -573,12 +573,10 @@ export default function MyInput({ value, onChange }) {
|
||||
}
|
||||
```
|
||||
|
||||
And then wrap it in [`forwardRef`](/reference/react/forwardRef) like this:
|
||||
And then add `ref` to the list of props your component accepts and pass `ref` as a prop to the relevent child [built-in component](/reference/react-dom/components/common) like this:
|
||||
|
||||
```js {3,8}
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
const MyInput = forwardRef(({ value, onChange }, ref) => {
|
||||
```js {1,6}
|
||||
function MyInput({ value, onChange, ref }) {
|
||||
return (
|
||||
<input
|
||||
value={value}
|
||||
@@ -586,7 +584,7 @@ const MyInput = forwardRef(({ value, onChange }, ref) => {
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default MyInput;
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user