From fb69425605f56ef107e0ba2e3e67cde2a6188ae7 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Fri, 13 Dec 2024 13:38:23 -0800 Subject: [PATCH 1/4] Remove forwardRef referecnes from useRef --- .../learn/manipulating-the-dom-with-refs.md | 83 +++---------------- src/content/reference/react/useRef.md | 67 +-------------- 2 files changed, 15 insertions(+), 135 deletions(-) diff --git a/src/content/learn/manipulating-the-dom-with-refs.md b/src/content/learn/manipulating-the-dom-with-refs.md index 6d20232fbf7..7da536b1c20 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 14cd9b2ecf3..e40794c3857 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) From b89d0b63fa57e0f76ce4c7c39a241869d04d7e40 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Fri, 13 Dec 2024 13:47:48 -0800 Subject: [PATCH 2/4] update wording for clarity --- src/content/learn/manipulating-the-dom-with-refs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/manipulating-the-dom-with-refs.md b/src/content/learn/manipulating-the-dom-with-refs.md index 7da536b1c20..a58ffd97cf8 100644 --- a/src/content/learn/manipulating-the-dom-with-refs.md +++ b/src/content/learn/manipulating-the-dom-with-refs.md @@ -382,7 +382,7 @@ export default function MyForm() { #### 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` 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): +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): From 649211b51f90709d643130efbb1209fdbfd873f7 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Tue, 17 Dec 2024 00:30:35 -0800 Subject: [PATCH 3/4] Address Dan's feedback --- .../learn/manipulating-the-dom-with-refs.md | 25 ++++++-- src/content/reference/react/useRef.md | 61 ++++++++++++++++++- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/content/learn/manipulating-the-dom-with-refs.md b/src/content/learn/manipulating-the-dom-with-refs.md index a58ffd97cf8..c1869a6315f 100644 --- a/src/content/learn/manipulating-the-dom-with-refs.md +++ b/src/content/learn/manipulating-the-dom-with-refs.md @@ -347,15 +347,32 @@ Read more about [how this helps find bugs](/reference/react/StrictMode#fixing-bu Refs are an escape hatch that should be used sparingly. Manually manipulating _another_ component's DOM nodes makes your code even more fragile. -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). +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 ; +} + +function MyForm() { + const inputRef = useRef(null); + return <> +} +``` + +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 ``. Because `` is a [built-in component](/reference/react-dom/components/common) React sets the `.current` property of the ref to the `` DOM element. + +The `inputRef` created in `MyForm` now points to the `` DOM element returned by `MyInput`. A click handler created in `MyForm` can access `inputRef` and call `focus()` to set the focus on ``. ```js import { useRef } from 'react'; -function MyInput(props) { - return ; +function MyInput({ref}) { + return ; } export default function MyForm() { @@ -530,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', diff --git a/src/content/reference/react/useRef.md b/src/content/reference/react/useRef.md index e40794c3857..4d7400fdbb3 100644 --- a/src/content/reference/react/useRef.md +++ b/src/content/reference/react/useRef.md @@ -455,8 +455,8 @@ Sometimes, you may want to let the parent component manipulate the DOM inside of ```js import { useRef } from 'react'; -const MyInput = (props, ref) => { - return ; +const MyInput = ({ref}) => { + return ; }; export default function Form() { @@ -535,3 +535,60 @@ 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: + + + +TypeError: Cannot read properties of null + + + +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 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 {1,6} +const MyInput = ({ 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) From 933ddc4688ff19b8b5128435bf8844a47e227a59 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Fri, 20 Dec 2024 10:41:24 -0800 Subject: [PATCH 4/4] Fix style mistakes --- src/content/learn/manipulating-the-dom-with-refs.md | 10 +++++----- src/content/reference/react/useRef.md | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/content/learn/manipulating-the-dom-with-refs.md b/src/content/learn/manipulating-the-dom-with-refs.md index c1869a6315f..e366ea7cc04 100644 --- a/src/content/learn/manipulating-the-dom-with-refs.md +++ b/src/content/learn/manipulating-the-dom-with-refs.md @@ -344,7 +344,7 @@ Read more about [how this helps find bugs](/reference/react/StrictMode#fixing-bu ## Accessing another component's DOM nodes {/*accessing-another-components-dom-nodes*/} -Refs are an escape hatch that should be used sparingly. Manually manipulating _another_ component's DOM nodes makes your code even more fragile. +Refs are an escape hatch. Manually manipulating _another_ component's DOM nodes can make your code fragile. You can pass refs from parent component to child components [just like any other prop](/learn/passing-props-to-a-component). @@ -352,13 +352,13 @@ You can pass refs from parent component to child components [just like any other ```js {3-4,9} import { useRef } from 'react'; -function MyInput({ref}) { +function MyInput({ ref }) { return ; } function MyForm() { const inputRef = useRef(null); - return <> + return } ``` @@ -371,7 +371,7 @@ The `inputRef` created in `MyForm` now points to the `` DOM element retur ```js import { useRef } from 'react'; -function MyInput({ref}) { +function MyInput({ ref }) { return ; } @@ -406,7 +406,7 @@ In the above example, the ref passed to `MyInput` is passed on to the original D ```js import { useRef, useImperativeHandle } from "react"; -const MyInput = ({ ref }) => { +function MyInput({ ref }) { const realInputRef = useRef(null); useImperativeHandle(ref, () => ({ // Only expose focus and nothing else diff --git a/src/content/reference/react/useRef.md b/src/content/reference/react/useRef.md index 4d7400fdbb3..8ab53aef371 100644 --- a/src/content/reference/react/useRef.md +++ b/src/content/reference/react/useRef.md @@ -455,7 +455,7 @@ Sometimes, you may want to let the parent component manipulate the DOM inside of ```js import { useRef } from 'react'; -const MyInput = ({ref}) => { +function MyInput({ ref }) { return ; }; @@ -576,7 +576,7 @@ export default function MyInput({ value, onChange }) { 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 {1,6} -const MyInput = ({ value, onChange, ref}) => { +function MyInput({ value, onChange, ref }) { return (