Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do we know when a React ref.current value has changed?

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.

like image 570
trusktr Avatar asked Apr 24 '19 20:04

trusktr


People also ask

How do you know if ref changes in React?

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.

Does useRef notify when its content changes?

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.

How do you check if the state has changed React?

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.


2 Answers

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>
like image 50
ford04 Avatar answered Oct 14 '22 04:10

ford04


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>
like image 42
Tholle Avatar answered Oct 14 '22 05:10

Tholle