-
Notifications
You must be signed in to change notification settings - Fork 7.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove forwardRef references from useRef and Manipulating the DOM with Refs pages #7364
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -343,75 +343,39 @@ 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 that should be used sparingly. Manually manipulating _another_ component's DOM nodes makes your code even more 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). | ||
|
||
<Sandpack> | ||
|
||
```js | ||
```js {3-4,9} | ||
import { useRef } from 'react'; | ||
|
||
function MyInput(props) { | ||
return <input {...props} />; | ||
function MyInput({ref}) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same nit |
||
return <input ref={ref} />; | ||
} | ||
|
||
export default function MyForm() { | ||
function MyForm() { | ||
const inputRef = useRef(null); | ||
|
||
function handleClick() { | ||
inputRef.current.focus(); | ||
} | ||
|
||
return ( | ||
<> | ||
<MyInput ref={inputRef} /> | ||
<button onClick={handleClick}> | ||
Focus the input | ||
</button> | ||
</> | ||
); | ||
return <><MyInput ref={inputRef} /></> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this |
||
} | ||
``` | ||
|
||
</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. | ||
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. | ||
|
||
Now clicking the button to focus the input works: | ||
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 { forwardRef, useRef } from 'react'; | ||
import { useRef } from 'react'; | ||
|
||
const MyInput = forwardRef((props, ref) => { | ||
return <input {...props} ref={ref} />; | ||
}); | ||
function MyInput({ref}) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: spacing |
||
return <input ref={ref} />; | ||
} | ||
|
||
export default function Form() { | ||
export default function MyForm() { | ||
const inputRef = useRef(null); | ||
|
||
function handleClick() { | ||
|
@@ -431,33 +395,27 @@ export default function Form() { | |
|
||
</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) => { | ||
const MyInput = ({ ref }) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should just be a function declaration now, no need for arrow function MyInput({ ref }) {
// ...
} |
||
const realInputRef = useRef(null); | ||
useImperativeHandle(ref, () => ({ | ||
// Only expose focus and nothing else | ||
focus() { | ||
realInputRef.current.focus(); | ||
}, | ||
})); | ||
return <input {...props} ref={realInputRef} />; | ||
}); | ||
return <input ref={realInputRef} />; | ||
}; | ||
|
||
export default function Form() { | ||
const inputRef = useRef(null); | ||
|
@@ -469,17 +427,15 @@ export default function Form() { | |
return ( | ||
<> | ||
<MyInput ref={inputRef} /> | ||
<button onClick={handleClick}> | ||
Focus the input | ||
</button> | ||
<button onClick={handleClick}>Focus the input</button> | ||
</> | ||
); | ||
} | ||
``` | ||
|
||
</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', | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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} />; | ||
}); | ||
const MyInput = ({ref}) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: we generally put spaces around destructuring like the rest of the docs also uses a function declaration style, like |
||
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,20 +573,18 @@ export default function MyInput({ value, onChange }) { | |
} | ||
``` | ||
And then wrap it in [`forwardRef`](/reference/react/forwardRef) like this: | ||
```js {3,8} | ||
import { forwardRef } from 'react'; | ||
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: | ||
const MyInput = forwardRef(({ value, onChange }, ref) => { | ||
```js {1,6} | ||
const MyInput = ({ value, onChange, ref}) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should remain a function declaration, no need to change it to an arrow export default function MyInput({ value, onChange, ref }) {
// ...
} also don't miss the space after |
||
return ( | ||
<input | ||
value={value} | ||
onChange={onChange} | ||
ref={ref} | ||
/> | ||
); | ||
}); | ||
}; | ||
|
||
export default MyInput; | ||
``` | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so i'm wondering if this is a bit too strong here... we're describing legit cases on this page (like focusing) but this makes it sound like even the stuff below is fragile