From beaab2abd0509092fc92e23bb59c5e98e46e5551 Mon Sep 17 00:00:00 2001 From: Joshua Slate Date: Fri, 6 Dec 2024 11:17:35 -0800 Subject: [PATCH 1/4] update useRef documentation to use ref prop instead of forwardRef --- .../learn/manipulating-the-dom-with-refs.md | 95 ++++++++----------- src/content/reference/react/useRef.md | 54 +++++------ 2 files changed, 62 insertions(+), 87 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..dabd98c1fb4 100644 --- a/src/content/learn/manipulating-the-dom-with-refs.md +++ b/src/content/learn/manipulating-the-dom-with-refs.md @@ -165,27 +165,27 @@ export default function CatFriends() { ```css div { - width: 100%; - overflow: hidden; + width: 100%; + overflow: hidden; } nav { - text-align: center; + text-align: center; } button { - margin: .25rem; + margin: .25rem; } ul, li { - list-style: none; - white-space: nowrap; + list-style: none; + white-space: nowrap; } li { - display: inline; - padding: 0.5rem; + display: inline; + padding: 0.5rem; } ``` @@ -285,27 +285,27 @@ function setupCatList() { ```css div { - width: 100%; - overflow: hidden; + width: 100%; + overflow: hidden; } nav { - text-align: center; + text-align: center; } button { - margin: .25rem; + margin: .25rem; } ul, li { - list-style: none; - white-space: nowrap; + list-style: none; + white-space: nowrap; } li { - display: inline; - padding: 0.5rem; + display: inline; + padding: 0.5rem; } ``` @@ -352,15 +352,15 @@ However, if you try to put a ref on **your own** component, like ``, ```js import { useRef } from 'react'; -function MyInput(props) { - return ; +function MyInput() { + return ; } export default function MyForm() { const inputRef = useRef(null); function handleClick() { - inputRef.current.focus(); + inputRef.current?.focus(); } return ( @@ -376,40 +376,31 @@ 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: +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 by passing the `ref` prop down. ```js -const MyInput = forwardRef((props, ref) => { +function MyInput({ ref, ...props }) { 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. +2. `MyInput` itself passes the `ref` prop it received to the `` inside of it. Now clicking the button to focus the input works: ```js -import { forwardRef, useRef } from 'react'; +import { useRef } from 'react'; -const MyInput = forwardRef((props, ref) => { +function MyInput({ ref, ...props }) { return ; -}); +} export default function Form() { const inputRef = useRef(null); @@ -442,13 +433,9 @@ In the above example, `MyInput` exposes the original DOM input element. This let ```js -import { - forwardRef, - useRef, - useImperativeHandle -} from 'react'; +import { useRef, useImperativeHandle } from 'react'; -const MyInput = forwardRef((props, ref) => { +function MyInput({ ref, ...props }) { const realInputRef = useRef(null); useImperativeHandle(ref, () => ({ // Only expose focus and nothing else @@ -457,7 +444,7 @@ const MyInput = forwardRef((props, ref) => { }, })); return ; -}); +} export default function Form() { const inputRef = useRef(null); @@ -691,7 +678,7 @@ However, this doesn't mean that you can't do it at all. It requires caution. **Y - Refs are a generic concept, but most often you'll use them to hold DOM elements. - You instruct React to put a DOM node into `myRef.current` by passing `
`. - Usually, you will use refs for non-destructive actions like focusing, scrolling, or measuring DOM elements. -- A component doesn't expose its DOM nodes by default. You can opt into exposing a DOM node by using `forwardRef` and passing the second `ref` argument down to a specific node. +- A component doesn't expose its DOM nodes by default. You can opt into exposing a DOM node by using a `ref` prop and passing it down to a specific node. - Avoid changing DOM nodes managed by React. - If you do modify DOM nodes managed by React, modify parts that React has no reason to update. @@ -1093,7 +1080,7 @@ Make it so that clicking the "Search" button puts focus into the field. Note tha -You'll need `forwardRef` to opt into exposing a DOM node from your own component like `SearchInput`. +You'll need the `ref` prop to opt into exposing a DOM node from your own component like `SearchInput`. @@ -1178,18 +1165,14 @@ export default function SearchButton({ onClick }) { ``` ```js src/SearchInput.js -import { forwardRef } from 'react'; - -export default forwardRef( - function SearchInput(props, ref) { - return ( - - ); - } -); +export default function SearchInput({ ref }) { + return ( + + ); +} ``` ```css diff --git a/src/content/reference/react/useRef.md b/src/content/reference/react/useRef.md index 14cd9b2ecf3..c640c4fbe5e 100644 --- a/src/content/reference/react/useRef.md +++ b/src/content/reference/react/useRef.md @@ -28,7 +28,7 @@ import { useRef } from 'react'; function MyComponent() { const intervalRef = useRef(0); const inputRef = useRef(null); - // ... +// ... ``` [See more examples below.](#usage) @@ -65,7 +65,7 @@ import { useRef } from 'react'; function Stopwatch() { const intervalRef = useRef(0); - // ... +// ... ``` `useRef` returns a ref object with a single `current` property initially set to the initial value you provided. @@ -245,22 +245,22 @@ import { useRef } from 'react'; function MyComponent() { const inputRef = useRef(null); - // ... +// ... ``` Then pass your ref object as the `ref` attribute to the JSX of the DOM node you want to manipulate: ```js [[1, 2, "inputRef"]] // ... - return ; +return ; ``` After React creates the DOM node and puts it on the screen, React will set the `current` property of your ref object to that DOM node. Now you can access the ``'s DOM node and call methods like [`focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus): ```js [[2, 2, "inputRef.current"]] function handleClick() { - inputRef.current.focus(); - } + inputRef.current.focus(); +} ``` React will set the `current` property back to `null` when the node is removed from the screen. @@ -365,27 +365,27 @@ export default function CatFriends() { ```css div { - width: 100%; - overflow: hidden; + width: 100%; + overflow: hidden; } nav { - text-align: center; + text-align: center; } button { - margin: .25rem; + margin: .25rem; } ul, li { - list-style: none; - white-space: nowrap; + list-style: none; + white-space: nowrap; } li { - display: inline; - padding: 0.5rem; + display: inline; + padding: 0.5rem; } ``` @@ -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 use a combination of `useRef` to hold the input and the `ref` prop to expose it to the parent 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) => { +function MyInput({ ref, ...props }) { return ; -}); +} export default function Form() { const inputRef = useRef(null); @@ -550,13 +550,7 @@ 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()? - - +You might find that `inputRef.current` is always `null`. By default, your own components don't expose refs to the DOM nodes inside them. @@ -573,12 +567,10 @@ 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 a `ref` prop and pass it to the DOM node that you want to reference: -const MyInput = forwardRef(({ value, onChange }, ref) => { +```js {1,6} +export default function MyInput({ value, onChange, ref }) { return ( { ref={ref} /> ); -}); +} export default MyInput; ``` From 64da1aef3def475b261d43c950ca037a2613997a Mon Sep 17 00:00:00 2001 From: Joshua Slate Date: Mon, 16 Dec 2024 09:50:31 -0800 Subject: [PATCH 2/4] remove whitespace changes --- .../learn/manipulating-the-dom-with-refs.md | 32 +++++++++---------- src/content/reference/react/useRef.md | 28 ++++++++-------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/content/learn/manipulating-the-dom-with-refs.md b/src/content/learn/manipulating-the-dom-with-refs.md index dabd98c1fb4..4cfb7c59e5e 100644 --- a/src/content/learn/manipulating-the-dom-with-refs.md +++ b/src/content/learn/manipulating-the-dom-with-refs.md @@ -165,27 +165,27 @@ export default function CatFriends() { ```css div { - width: 100%; - overflow: hidden; + width: 100%; + overflow: hidden; } nav { - text-align: center; + text-align: center; } button { - margin: .25rem; + margin: .25rem; } ul, li { - list-style: none; - white-space: nowrap; + list-style: none; + white-space: nowrap; } li { - display: inline; - padding: 0.5rem; + display: inline; + padding: 0.5rem; } ``` @@ -285,27 +285,27 @@ function setupCatList() { ```css div { - width: 100%; - overflow: hidden; + width: 100%; + overflow: hidden; } nav { - text-align: center; + text-align: center; } button { - margin: .25rem; + margin: .25rem; } ul, li { - list-style: none; - white-space: nowrap; + list-style: none; + white-space: nowrap; } li { - display: inline; - padding: 0.5rem; + display: inline; + padding: 0.5rem; } ``` diff --git a/src/content/reference/react/useRef.md b/src/content/reference/react/useRef.md index c640c4fbe5e..3ca2cba6165 100644 --- a/src/content/reference/react/useRef.md +++ b/src/content/reference/react/useRef.md @@ -28,7 +28,7 @@ import { useRef } from 'react'; function MyComponent() { const intervalRef = useRef(0); const inputRef = useRef(null); -// ... + // ... ``` [See more examples below.](#usage) @@ -65,7 +65,7 @@ import { useRef } from 'react'; function Stopwatch() { const intervalRef = useRef(0); -// ... + // ... ``` `useRef` returns a ref object with a single `current` property initially set to the initial value you provided. @@ -245,22 +245,22 @@ import { useRef } from 'react'; function MyComponent() { const inputRef = useRef(null); -// ... + // ... ``` Then pass your ref object as the `ref` attribute to the JSX of the DOM node you want to manipulate: ```js [[1, 2, "inputRef"]] // ... -return ; + return ; ``` After React creates the DOM node and puts it on the screen, React will set the `current` property of your ref object to that DOM node. Now you can access the ``'s DOM node and call methods like [`focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus): ```js [[2, 2, "inputRef.current"]] function handleClick() { - inputRef.current.focus(); -} + inputRef.current.focus(); + } ``` React will set the `current` property back to `null` when the node is removed from the screen. @@ -365,27 +365,27 @@ export default function CatFriends() { ```css div { - width: 100%; - overflow: hidden; + width: 100%; + overflow: hidden; } nav { - text-align: center; + text-align: center; } button { - margin: .25rem; + margin: .25rem; } ul, li { - list-style: none; - white-space: nowrap; + list-style: none; + white-space: nowrap; } li { - display: inline; - padding: 0.5rem; + display: inline; + padding: 0.5rem; } ``` From a945321954b22be05fee7628f610a3fcfa41d1b2 Mon Sep 17 00:00:00 2001 From: Joshua Slate Date: Mon, 16 Dec 2024 10:25:06 -0800 Subject: [PATCH 3/4] remove unused props and optional chaining, explain crash --- .../learn/manipulating-the-dom-with-refs.md | 16 +++++++++------- src/content/reference/react/useRef.md | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/content/learn/manipulating-the-dom-with-refs.md b/src/content/learn/manipulating-the-dom-with-refs.md index 4cfb7c59e5e..411cfb9a2bb 100644 --- a/src/content/learn/manipulating-the-dom-with-refs.md +++ b/src/content/learn/manipulating-the-dom-with-refs.md @@ -360,7 +360,7 @@ export default function MyForm() { const inputRef = useRef(null); function handleClick() { - inputRef.current?.focus(); + inputRef.current.focus(); } return ( @@ -376,13 +376,15 @@ export default function MyForm() { +Instead, the application crashes because `inputRef.current` is `null`, due to `` not passing the `ref` prop down to one of its children. + 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 by passing the `ref` prop down. ```js -function MyInput({ ref, ...props }) { - return ; +function MyInput({ ref }) { + return ; } ``` @@ -398,8 +400,8 @@ Now clicking the button to focus the input works: ```js import { useRef } from 'react'; -function MyInput({ ref, ...props }) { - return ; +function MyInput({ ref }) { + return ; } export default function Form() { @@ -435,7 +437,7 @@ In the above example, `MyInput` exposes the original DOM input element. This let ```js import { useRef, useImperativeHandle } from 'react'; -function MyInput({ ref, ...props }) { +function MyInput({ ref }) { const realInputRef = useRef(null); useImperativeHandle(ref, () => ({ // Only expose focus and nothing else @@ -443,7 +445,7 @@ function MyInput({ ref, ...props }) { realInputRef.current.focus(); }, })); - return ; + return ; } export default function Form() { diff --git a/src/content/reference/react/useRef.md b/src/content/reference/react/useRef.md index 3ca2cba6165..d7c8211e6c1 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'; -function MyInput({ ref, ...props }) { - return ; +function MyInput({ ref }) { + return ; } export default function Form() { From 4d77acd2600bf65010d0a8da9f922f3a512d11ba Mon Sep 17 00:00:00 2001 From: Joshua Slate Date: Mon, 16 Dec 2024 10:49:37 -0800 Subject: [PATCH 4/4] add back code block with error message --- src/content/learn/manipulating-the-dom-with-refs.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/content/learn/manipulating-the-dom-with-refs.md b/src/content/learn/manipulating-the-dom-with-refs.md index 411cfb9a2bb..f7c3c45d5cb 100644 --- a/src/content/learn/manipulating-the-dom-with-refs.md +++ b/src/content/learn/manipulating-the-dom-with-refs.md @@ -378,6 +378,10 @@ export default function MyForm() { Instead, the application crashes because `inputRef.current` is `null`, due to `` not passing the `ref` prop down to one of its children. + +Cannot read properties of null (reading 'focus') + + 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 by passing the `ref` prop down.