You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -540,41 +540,96 @@ Here, the `playerRef` itself is nullable. However, you should be able to convinc
540
540
541
541
### Detect DOM changes with a ref {/*detect-dom-changes-with-a-ref*/}
542
542
543
-
In some scenarios, you might need to detect changes in the DOM, such as when a component's children are dynamically updated. You can achieve this by using a `ref` callback wrapped in `useCallback` to create a MutationObserver. This approach allows you to observe changes in the DOM and perform actions based on those changes.
543
+
In some situations, you might need to detect changes in the DOM, such as when a 3rd party library draws visualizations directly to the DOM. To do so, first create a ref callback: a function passed to the ref attribute of the DOM node you want to observe. The ref callback takes a single argument: the DOM node you'd like to observe. Wrap your ref callback in `useCallback` to [prevent unnecessary reconnections](#how-to-avoid-callback-reconnections-with-usecallback).
544
+
545
+
```js {5,10}
546
+
import { useRef, useCallback } from"react";
547
+
548
+
functionLogo() {
549
+
constlogoRef=useRef(null);
550
+
constsetLogoRef=useCallback((node) => {
551
+
logoRef.current= node;
552
+
//...
553
+
}, []);
554
+
//...
555
+
return<div ref={setLogoRef}></div>
556
+
}
557
+
```
558
+
559
+
Next, set up a [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to monitor changes and handle those changes accordingly in your ref callback. In this example, the MutationObserver is monitoring for changes to the children of a `<div>`. Don't forget to disconnect your observer when you no longer need to monitor for changes.
560
+
561
+
```js {7-13}
562
+
import { useRef, useCallback } from"react";
563
+
564
+
functionLogo() {
565
+
constlogoRef=useRef(null);
566
+
constsetLogoRef=useCallback((node) => {
567
+
logoRef.current= node;
568
+
constobserver=newMutationObserver(() => {
569
+
if (node &&node.children.length>0) {
570
+
// TODO: handle when children are added to this DOM node
571
+
observer.disconnect();
572
+
}
573
+
});
574
+
observer.observe(node, { childList:true });
575
+
}, []);
576
+
//...
577
+
return<div ref={setLogoRef}></div>
578
+
}
579
+
```
580
+
581
+
Lastly, you'll need to return a function from your ref callback to cleanup the observer and ref. Explicitly setting the ref to null during cleanup prevents [refs to unmounted DOM nodes](#how-to-avoid-a-ref-to-a-unmounted-node).
In this example, the `Logo` component utilizes a `MutationObserver` to detect when child elements are added to a `<div>` allowing it to update the component's state and stop displaying a loading indicator once the logo is fully drawn. Tap the "Reset" button in the upper right corner of the CodeSandbox example below to see how the loading indicator is replaced by the logo.
@@ -649,12 +704,14 @@ function drawReactLogo(node) {
649
704
650
705
<DeepDive>
651
706
652
-
#### Prevent reconnections with useCallback {/*prevent-listener-reconnections-with-usecallback*/}
707
+
#### How to avoid callback reconnections with useCallback {/*how-to-avoid-callback-reconnections-with-usecallback*/}
653
708
654
-
When a ref callback function change, React will disconnect and reconnect on render. This is similar to a function dependency in an effect. React does this because new prop values may be needed to be passed to the ref callback function.
709
+
When React re-renders a component, all the functions defined in the component are recreated. This includes ref callback functions defined in components. When a ref callback function is changed or recreated, React will disconnect and reconnect your ref callback function. React does this because new prop values may need to be passed to the ref callback function. This is similar to a function dependency in an effect.
655
710
656
-
```js
711
+
```js {4}
657
712
exportdefaultfunctionReactLogo() {
713
+
// 🚩 without useCallback, the callback changes every
714
+
// render, which causes the listener to reconnect
658
715
constsetLogoRef= (node) => {
659
716
//...
660
717
};
@@ -663,10 +720,14 @@ export default function ReactLogo() {
663
720
}
664
721
```
665
722
666
-
To avoid unnecessary reconnections wrap your ref callback function in [useCallback](/reference/react/useCallback). Make sure to add any dependancies to the `useCallback` dependency array. This will ensure the ref callback is called with updated props when necessary.
723
+
To disconnect, React will call your ref callback function with `null` as an argument. To reconnect, React calls your ref callback function with the DOM node as an argument.
724
+
725
+
To avoid unnecessary disconnections and reconnections wrap your ref callback function in [useCallback](/reference/react/useCallback). Make sure to add any dependencies to the `useCallback` dependency array. This will ensure the ref callback is called with updated props when necessary.
667
726
668
-
```js {2,4}
727
+
```js {4,6}
669
728
exportdefaultfunctionReactLogo() {
729
+
// ✅ with useCallback, the callback is stable
730
+
// so the listener doesn't reconnect each render
670
731
constsetLogoRef=useCallback((node) => {
671
732
//....
672
733
}, []);
@@ -679,89 +740,51 @@ export default function ReactLogo() {
679
740
680
741
<DeepDive>
681
742
682
-
#### Avoiding Stale Refs {/*avoiding-stale-refs*/}
743
+
#### How to avoid a ref to a unmounted node {/*how-to-avoid-a-ref-to-a-unmounted-node*/}
683
744
684
-
A `ref` callback function with a cleanup function that does not set `ref.current` to `null` can result in a `ref` to a unmounted node. Uncheck "Show Input" below and click "Submit" to see how the `ref` to the unmounted `<input>` is still accessible by the click handler for the form.
685
-
686
-
<Sandpack>
745
+
A `ref` callback function with a cleanup function that does not set `ref.current` to `null` can result in a `ref` to a unmounted node.
687
746
688
747
```js
689
-
import { useRef, useState } from"react";
748
+
exportdefaultfunctionLogo() {
749
+
constlogoRef=useRef(null);
750
+
constsetLogoRef=useCallback((node) => {
751
+
logoRef.current= node;
752
+
//...
690
753
691
-
exportdefaultfunctionMyForm() {
692
-
const [showInput, setShowInput] =useState(true);
693
-
constinputRef=useRef();
694
-
consthandleCheckboxChange= (event) => {
695
-
setShowInput(event.target.checked);
696
-
};
697
-
consthandleSubmit= (event) => {
698
-
event.preventDefault();
699
-
if (inputRef.current) {
700
-
alert(`Input value is: "${inputRef.current.value}"`);
701
-
} else {
702
-
alert("no input");
703
-
}
704
-
};
705
-
constinputRefCallback= (node) => {
706
-
inputRef.current= node;
754
+
// 🚩 if your ref cleanup function does not explicitly
755
+
// set the ref to null the ref may point to a
756
+
// unmounted DOM node
707
757
return () => {
708
-
// ⚠️ You must set `ref.current` to `null`
709
-
// in this cleanup function e.g.
710
-
// `inputRef.current = null;`
711
-
// to prevent hanging refs to unmounted DOM nodes
758
+
observer.disconnect();
712
759
};
713
-
};
714
-
715
-
return (
716
-
<form onSubmit={handleSubmit}>
717
-
<div>
718
-
<label>
719
-
<input
720
-
type="checkbox"
721
-
checked={showInput}
722
-
onChange={handleCheckboxChange}
723
-
/>
724
-
Show Input
725
-
</label>
726
-
</div>
727
-
{showInput && (
728
-
<div>
729
-
<label>
730
-
Input:
731
-
<input
732
-
type="text"
733
-
defaultValue="value from input DOM node"
734
-
ref={inputRefCallback}
735
-
/>
736
-
</label>
737
-
</div>
738
-
)}
739
-
<button type="submit">Submit</button>
740
-
</form>
741
-
);
760
+
}, []);
761
+
//...
762
+
return<div ref={setLogoRef}></div>
742
763
}
743
764
```
744
765
745
-
</Sandpack>
746
-
747
766
To fix the hanging ref to the DOM node that is no longer rendered, set `ref.current` to `null` in the `ref` callback cleanup function.
748
767
749
-
```js
750
-
import { useRef } from"react";
768
+
```js {11}
769
+
exportdefaultfunctionLogo() {
770
+
constlogoRef=useRef(null);
771
+
constsetLogoRef=useCallback((node) => {
772
+
logoRef.current= node;
773
+
//...
751
774
752
-
functionMyInput() {
753
-
constinputRef=useRef()
754
-
constinputRefCallback= (node) => {
755
-
inputRef.current= node;
756
775
return () => {
757
-
// ⚠️ You must set `ref.current` to `null` in this cleanup
758
-
// function to prevent hanging refs to unmounted DOM nodes
0 commit comments