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.
To check know when a React ref. current value has changed, we can add a callback ref to watch for the ref's value with a function. We call the useCallback hook with a function that takes the node parameter. node is the ref.
Keep in mind that useRef doesn't notify you when its content changes. Mutating the . current record field doesn't cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.
Use the useEffect hook to listen for state changes in React. You can add the state variables you want to track to the hook's dependencies array and the logic in your useEffect hook will run every time the state variables change.
React docs recommend using callback refs to detect ref
value changes.
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 // ... }, []);
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>
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With