Skip to content
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

update useRef documentation to use ref prop instead of forwardRef #7332

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 39 additions & 56 deletions src/content/learn/manipulating-the-dom-with-refs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
```

Expand Down Expand Up @@ -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;
}
```

Expand Down Expand Up @@ -352,15 +352,15 @@ However, if you try to put a ref on **your own** component, like `<MyInput />`,
```js
import { useRef } from 'react';

function MyInput(props) {
return <input {...props} />;
function MyInput() {
return <input />;
}

export default function MyForm() {
const inputRef = useRef(null);

function handleClick() {
inputRef.current.focus();
inputRef.current?.focus();
}

return (
Expand All @@ -376,40 +376,31 @@ 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:
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 <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.
2. `MyInput` itself passes the `ref` prop it received to the `<input>` inside of it.

Now clicking the button to focus the input works:

<Sandpack>

```js
import { forwardRef, useRef } from 'react';
import { useRef } from 'react';

const MyInput = forwardRef((props, ref) => {
function MyInput({ ref, ...props }) {
return <input {...props} ref={ref} />;
});
}

export default function Form() {
const inputRef = useRef(null);
Expand Down Expand Up @@ -442,13 +433,9 @@ In the above example, `MyInput` exposes the original DOM input element. This let
<Sandpack>

```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
Expand All @@ -457,7 +444,7 @@ const MyInput = forwardRef((props, ref) => {
},
}));
return <input {...props} ref={realInputRef} />;
});
}

export default function Form() {
const inputRef = useRef(null);
Expand Down Expand Up @@ -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 `<div ref={myRef}>`.
- 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.

Expand Down Expand Up @@ -1093,7 +1080,7 @@ Make it so that clicking the "Search" button puts focus into the field. Note tha

<Hint>

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`.

</Hint>

Expand Down Expand Up @@ -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 (
<input
ref={ref}
placeholder="Looking for something?"
/>
);
}
);
export default function SearchInput({ ref }) {
return (
<input
ref={ref}
placeholder="Looking for something?"
/>
);
}
```

```css
Expand Down
54 changes: 23 additions & 31 deletions src/content/reference/react/useRef.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { useRef } from 'react';
function MyComponent() {
const intervalRef = useRef(0);
const inputRef = useRef(null);
// ...
// ...
```

[See more examples below.](#usage)
Expand Down Expand Up @@ -65,7 +65,7 @@ import { useRef } from 'react';

function Stopwatch() {
const intervalRef = useRef(0);
// ...
// ...
```

`useRef` returns a <CodeStep step={1}>ref object</CodeStep> with a single <CodeStep step={2}>`current` property</CodeStep> initially set to the <CodeStep step={3}>initial value</CodeStep> you provided.
Expand Down Expand Up @@ -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 <input ref={inputRef} />;
return <input ref={inputRef} />;
```

After React creates the DOM node and puts it on the screen, React will set the <CodeStep step={2}>`current` property</CodeStep> of your ref object to that DOM node. Now you can access the `<input>`'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.
Expand Down Expand Up @@ -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;
}
```

Expand Down Expand Up @@ -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.

<Sandpack>

```js
import { forwardRef, useRef } from 'react';
import { useRef } from 'react';

const MyInput = forwardRef((props, ref) => {
function MyInput({ ref, ...props }) {
return <input {...props} ref={ref} />;
});
}

export default function Form() {
const inputRef = useRef(null);
Expand Down Expand Up @@ -550,13 +550,7 @@ const inputRef = useRef(null);
return <MyInput ref={inputRef} />;
```

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()?

</ConsoleBlock>
You might find that `inputRef.current` is always `null`.

By default, your own components don't expose refs to the DOM nodes inside them.

Expand All @@ -573,20 +567,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 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 (
<input
value={value}
onChange={onChange}
ref={ref}
/>
);
});
}

export default MyInput;
```
Expand Down
Loading