6

I have a class that needs to get the size of a DOM element. It works well, but when I resize the window it doesn't update until I change the state in my app, forcing a rerender. I've tried adding this.forceUpdate to a 'resize' event listener in componentDidMount(), but it didn't work. Perhaps I did something wrong? Ideally I'd like to avoid using this.forceUpdate for perfomance implications anyway. Any work arounds for this? Thanks in advance!

My code:

class MyComponent extends React.Component {
  state = { x: 0, y: 0 }

  refCallback = (element) => {
    if (!element) {
      return
    }
    const { x, y } = element.getBoundingClientRect()
    this.setState({ x, y })
  }

  render() {
    console.log('STATE:', this.state) // Outputs the correct x and y values.
    return (
      <div ref={this.refCallback}>
        <button>Hello world</button>
      </div>
    )
  }
}
cameraguy258
  • 609
  • 2
  • 9
  • 21
  • Did you add a ```resize``` event listener on the ```window``` object? That's the element where a browser resize event is fired. You can see an [example](https://stackoverflow.com/questions/19014250/rerender-view-on-browser-resize-with-react) of how this is done with hooks in this question, but basically the same method can be used with a class component, where you write a function that updates your state and attach it to the window. – Chris B. Feb 14 '20 at 18:28
  • @ChrisB. I've already given that a try – slashmelon Feb 14 '20 at 18:34

2 Answers2

7

If you want to measure some element in your component whenever the window resizes, it's going to look something like this:

class MyComponent extends React.Component {
  state = {
    x: 0,
    y: 0,
  };

  element = React.createRef();

  onWindowResize = () => {
    if (this.element.current) {
      const {x, y} = this.element.current.getBoundingClientRect();
      this.setState({x, y}, () => {
        console.log(this.state);
      });
    }
  };

  componentDidMount() {
    window.addEventListener('resize', this.onWindowResize);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.onWindowResize);
  }

  render() {
    return (
      <div ref={this.element}>
        <button>Hello, World</button>
      </div>
    );
  }
}

The trick here is that your ref callback is only called once, when the element is initially added to the DOM. If you want to update state whenever you resize the window, you're going to need a 'resize' event handler.

rossipedia
  • 56,800
  • 10
  • 90
  • 93
1

That happening because:

From the React documentation:

Adding a Ref to a DOM Element

React supports a special attribute that you can attach to any component. The ref attribute takes a callback function, and the callback will be executed immediately after the component is mounted or unmounted.

React will call the ref callback with the DOM element when the component mounts, and call it with null when it unmounts.

So, that's why when you refresh you get the value. To overcome the problem you can do something like this:

import React from "react";

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
    this.state = {
      x: 0,
      y: 0
    };
  }

  updateDimensions = () => {
    if (this.myRef.current) {
      const {x, y} = this.myRef.current.getBoundingClientRect();
      this.setState({ x, y });
    }
  };
  componentDidMount() {
    window.addEventListener("resize", this.updateDimensions);
  }
  componentWillUnmount() {
    window.removeEventListener("resize", this.updateDimensions);
  }

  render() {
    console.log("STATE:", this.state); // Outputs the correct x and y values.
    return (
      <div ref={this.myRef}>
        <button>Hello world</button>
      </div>
    );
  }
}

export default MyComponent;

Hope this works for you.

Community
  • 1
  • 1
Muhammad Zeeshan
  • 4,608
  • 4
  • 15
  • 41