diff --git a/src/content/learn/manipulating-the-dom-with-refs.md b/src/content/learn/manipulating-the-dom-with-refs.md index 6d20232fb..7da536b1c 100644 --- a/src/content/learn/manipulating-the-dom-with-refs.md +++ b/src/content/learn/manipulating-the-dom-with-refs.md @@ -343,9 +343,11 @@ 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 ``, React will set that ref's `current` property to the corresponding DOM node (such as the actual `` in the browser). + +Refs are an escape hatch that should be used sparingly. Manually manipulating _another_ component's DOM nodes makes your code even more fragile. + -However, if you try to put a ref on **your own** component, like ``, by default you will get `null`. Here is an example demonstrating it. Notice how clicking the button **does not** focus the input: +When you put a ref on a built-in component that outputs a browser element like ``, React will set that ref's `current` property to the corresponding DOM node (such as the actual `` in the browser). @@ -376,79 +378,18 @@ export default function MyForm() { -To help you notice the issue, React also prints an error to the console: - - - -Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? - - - -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 ; -}); -``` - -This is how it works: - -1. `` 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 `` inside of it. - -Now clicking the button to focus the input works: - - - -```js -import { forwardRef, useRef } from 'react'; - -const MyInput = forwardRef((props, ref) => { - return ; -}); - -export default function Form() { - const inputRef = useRef(null); - - function handleClick() { - inputRef.current.focus(); - } - - return ( - <> - - - - ); -} -``` - - - -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. - #### 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, `MyInput` ref props 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): ```js -import { - forwardRef, - useRef, - useImperativeHandle -} from 'react'; +import { useRef, useImperativeHandle } from "react"; -const MyInput = forwardRef((props, ref) => { +const MyInput = ({ ref }) => { const realInputRef = useRef(null); useImperativeHandle(ref, () => ({ // Only expose focus and nothing else @@ -456,8 +397,8 @@ const MyInput = forwardRef((props, ref) => { realInputRef.current.focus(); }, })); - return ; -}); + return ; +}; export default function Form() { const inputRef = useRef(null); @@ -469,9 +410,7 @@ export default function Form() { return ( <> - + ); } @@ -479,7 +418,7 @@ export default function Form() { -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. diff --git a/src/content/reference/react/useRef.md b/src/content/reference/react/useRef.md index 14cd9b2ec..e40794c38 100644 --- a/src/content/reference/react/useRef.md +++ b/src/content/reference/react/useRef.md @@ -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. ```js -import { forwardRef, useRef } from 'react'; +import { useRef } from 'react'; -const MyInput = forwardRef((props, ref) => { +const MyInput = (props, ref) => { return ; -}); +}; export default function Form() { const inputRef = useRef(null); @@ -535,62 +535,3 @@ function Video() { Here, the `playerRef` itself is nullable. However, you should be able to convince your type checker that there is no case in which `getPlayer()` returns `null`. Then use `getPlayer()` in your event handlers. - ---- - -## Troubleshooting {/*troubleshooting*/} - -### I can't get a ref to a custom component {/*i-cant-get-a-ref-to-a-custom-component*/} - -If you try to pass a `ref` to your own component like this: - -```js -const inputRef = useRef(null); - -return ; -``` - -You might get an error in the console: - - - -Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? - - - -By default, your own components don't expose refs to the DOM nodes inside them. - -To fix this, find the component that you want to get a ref to: - -```js -export default function MyInput({ value, onChange }) { - return ( - - ); -} -``` - -And then wrap it in [`forwardRef`](/reference/react/forwardRef) like this: - -```js {3,8} -import { forwardRef } from 'react'; - -const MyInput = forwardRef(({ value, onChange }, ref) => { - return ( - - ); -}); - -export default MyInput; -``` - -Then the parent component can get a ref to it. - -Read more about [accessing another component's DOM nodes.](/learn/manipulating-the-dom-with-refs#accessing-another-components-dom-nodes)