Skip to content

Commit

Permalink
Add documentation for ref cleanup functions (#6770)
Browse files Browse the repository at this point in the history
* Add documentation for ref cleanup functions

* Contain changes within canary block
  • Loading branch information
jackpope authored Apr 24, 2024
1 parent f8afd94 commit cdd2fdd
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 33 deletions.
87 changes: 56 additions & 31 deletions src/content/learn/manipulating-the-dom-with-refs.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,18 +218,19 @@ This example shows how you can use this approach to scroll to an arbitrary node
<Sandpack>

```js
import { useRef } from 'react';
import { useRef, useState } from "react";

export default function CatFriends() {
const itemsRef = useRef(null);
const [catList, setCatList] = useState(setupCatList);

function scrollToId(itemId) {
function scrollToCat(cat) {
const map = getMap();
const node = map.get(itemId);
const node = map.get(cat);
node.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center'
behavior: "smooth",
block: "nearest",
inline: "center",
});
}

Expand All @@ -244,34 +245,25 @@ export default function CatFriends() {
return (
<>
<nav>
<button onClick={() => scrollToId(0)}>
Tom
</button>
<button onClick={() => scrollToId(5)}>
Maru
</button>
<button onClick={() => scrollToId(9)}>
Jellylorum
</button>
<button onClick={() => scrollToCat(catList[0])}>Tom</button>
<button onClick={() => scrollToCat(catList[5])}>Maru</button>
<button onClick={() => scrollToCat(catList[9])}>Jellylorum</button>
</nav>
<div>
<ul>
{catList.map(cat => (
{catList.map((cat) => (
<li
key={cat.id}
key={cat}
ref={(node) => {
const map = getMap();
if (node) {
map.set(cat.id, node);
map.set(cat, node);
} else {
map.delete(cat.id);
map.delete(cat);
}
}}
>
<img
src={cat.imageUrl}
alt={'Cat #' + cat.id}
/>
<img src={cat} />
</li>
))}
</ul>
Expand All @@ -280,12 +272,13 @@ export default function CatFriends() {
);
}

const catList = [];
for (let i = 0; i < 10; i++) {
catList.push({
id: i,
imageUrl: 'https://placekitten.com/250/200?image=' + i
});
function setupCatList() {
const catList = [];
for (let i = 0; i < 10; i++) {
catList.push("https://loremflickr.com/320/240/cat?lock=" + i);
}

return catList;
}

```
Expand Down Expand Up @@ -316,6 +309,16 @@ li {
}
```

```json package.json hidden
{
"dependencies": {
"react": "canary",
"react-dom": "canary",
"react-scripts": "^5.0.0"
}
}
```

</Sandpack>

In this example, `itemsRef` doesn't hold a single DOM node. Instead, it holds a [Map](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map) from item ID to a DOM node. ([Refs can hold any values!](/learn/referencing-values-with-refs)) The [`ref` callback](/reference/react-dom/components/common#ref-callback) on every list item takes care to update the Map:
Expand All @@ -327,17 +330,39 @@ In this example, `itemsRef` doesn't hold a single DOM node. Instead, it holds a
const map = getMap();
if (node) {
// Add to the Map
map.set(cat.id, node);
map.set(cat, node);
} else {
// Remove from the Map
map.delete(cat.id);
map.delete(cat);
}
}}
>
```

This lets you read individual DOM nodes from the Map later.

<Canary>

This example shows another approach for managing the Map with a `ref` callback cleanup function.

```js
<li
key={cat.id}
ref={node => {
const map = getMap();
// Add to the Map
map.set(cat, node);

return () => {
// Remove from the Map
map.delete(cat);
};
}}
>
```

</Canary>

</DeepDive>

## Accessing another component's DOM nodes {/*accessing-another-components-dom-nodes*/}
Expand Down
25 changes: 23 additions & 2 deletions src/content/reference/react-dom/components/common.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,32 @@ React will also call your `ref` callback whenever you pass a *different* `ref` c

#### Parameters {/*ref-callback-parameters*/}

* `node`: A DOM node or `null`. React will pass you the DOM node when the ref gets attached, and `null` when the ref gets detached. Unless you pass the same function reference for the `ref` callback on every render, the callback will get temporarily detached and re-attached during every re-render of the component.
* `node`: A DOM node or `null`. React will pass you the DOM node when the ref gets attached, and `null` when the `ref` gets detached. Unless you pass the same function reference for the `ref` callback on every render, the callback will get temporarily detached and re-attached during every re-render of the component.

<Canary>

#### Returns {/*returns*/}

Do not return anything from the `ref` callback.
* **optional** `cleanup function`: When the `ref` is detached, React will call the cleanup function. If a function is not returned by the `ref` callback, React will call the callback again with `null` as the argument when the `ref` gets detached.

```js

<div ref={(node) => {
console.log(node);

return () => {
console.log('Clean up', node)
}
}}>

```

#### Caveats {/*caveats*/}

* When Strict Mode is on, React will **run one extra development-only setup+cleanup cycle** before the first real setup. This is a stress-test that ensures that your cleanup logic "mirrors" your setup logic and that it stops or undoes whatever the setup is doing. If this causes a problem, implement the cleanup function.
* When you pass a *different* `ref` callback, React will call the *previous* callback's cleanup function if provided. If not cleanup function is defined, the `ref` callback will be called with `null` as the argument. The *next* function will be called with the DOM node.

</Canary>

---

Expand Down

0 comments on commit cdd2fdd

Please sign in to comment.