How do we know when a React ref.current value has changed?
Solution 1
React docs recommend using callback refs to detect ref
value changes.
Hooks
export function Comp() {
const onRefChange = useCallback(node => {
if (node === null) {
// DOM node referenced by ref has been unmounted
} else {
// DOM node referenced by ref has changed and exists
}
}, []); // adjust deps
return <h1 ref={onRefChange}>Hey</h1>;
}
useCallback
is used to prevent double calling of ref callback with null
and the element.
You can trigger re-renders on change by storing the current DOM node with useState
:
const [domNode, setDomNode] = useState(null);
const onRefChange = useCallback(node => {
setDomNode(node); // trigger re-render on changes
// ...
}, []);
Class component
export class FooClass extends React.Component {
state = { ref: null, ... };
onRefChange = node => {
// same as Hooks example, re-render on changes
this.setState({ ref: node });
};
render() {
return <h1 ref={this.onRefChange}>Hey</h1>;
}
}
Note: useRef
doesn't notify of ref
changes. Also no luck with React.createRef()
/ object refs.
Here is a test case, that drops and re-adds a node while triggering onRefChange
callback :
const Foo = () => {
const [ref, setRef] = useState(null);
const [removed, remove] = useState(false);
useEffect(() => {
setTimeout(() => remove(true), 3000); // drop after 3 sec
setTimeout(() => remove(false), 5000); // ... and mount it again
}, []);
const onRefChange = useCallback(node => {
console.log("ref changed to:", node);
setRef(node); // or change other state to re-render
}, []);
return !removed && <h3 ref={onRefChange}>Hello, world</h3>;
}
ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.1/umd/react.production.min.js" integrity="sha256-vMEjoeSlzpWvres5mDlxmSKxx6jAmDNY4zCt712YCI0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.1/umd/react-dom.production.min.js" integrity="sha256-QQt6MpTdAD0DiPLhqhzVyPs1flIdstR4/R7x4GqCvZ4=" crossorigin="anonymous"></script>
<script> var {useState, useEffect, useCallback} = React</script>
<div id="root"></div>
Solution 2
componentDidUpdate
is invoked when the component state or props change, so it will not necessarily be invoked when a ref
changes since it can be mutated as you see fit.
If you want to check if a ref has changed from previous render though, you can keep another ref that you check against the real one.
Example
class App extends React.Component {
prevRef = null;
ref = React.createRef();
state = {
isVisible: true
};
componentDidMount() {
this.prevRef = this.ref.current;
setTimeout(() => {
this.setState({ isVisible: false });
}, 1000);
}
componentDidUpdate() {
if (this.prevRef !== this.ref.current) {
console.log("ref changed!");
}
this.prevRef = this.ref.current;
}
render() {
return this.state.isVisible ? <div ref={this.ref}>Foo</div> : null;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
trusktr
Joe Pea trusktr.io (personal site) lume.io (open source 3D toolkit I'm working on) twitter github codepen
Updated on July 09, 2022Comments
-
trusktr almost 2 years
Normally, with props, we can write
componentDidUpdate(oldProps) { if (oldProps.foo !== this.props.foo) { console.log('foo prop changed') } }
in order to detect prop changes.
But if we use
React.createRef()
, how to we detect when a ref has changed to a new component or DOM element? The React docs don't really mention anything.F.e.,
class Foo extends React.Component { someRef = React.createRef() componentDidUpdate(oldProps) { const refChanged = /* What do we put here? */ if (refChanged) { console.log('new ref value:', this.someRef.current) } } render() { // ... } }
Are we supposed to implement some sort of old-value thing ourselves?
F.e.,
class Foo extends React.Component { someRef = React.createRef() oldRef = {} componentDidMount() { this.oldRef.current = this.someRef.current } componentDidUpdate(oldProps) { const refChanged = this.oldRef.current !== this.someRef.current if (refChanged) { console.log('new ref value:', this.someRef.current) this.oldRef.current = this.someRef.current } } render() { // ... } }
Is that what we're supposed to do? I would've thought that React would've baked in some sort of easy feature for this.