souporserious/react-measure

Understanding how measureRef works

Opened this issue · 6 comments

I've been running into an endless update loop and am having no luck making a reduced case yet nor understanding the issue so I thought I'd post something and see if anyone has any tips or ideas.

The issue I'm having is my onResize is being called constantly. I'm checking that width and height equal what I already recorded and they are the same. Adding lots of console logs I see that the the measureRef ref callback is getting called constantly as in

class SomeComponent extends React.Component ...
  constructor(props) {
    super(props);
    this._handleResize = this._handleResize.bind(this);
  }
  _handleResize() {
     console.log('handleResize');
  }
  render() {
    console.log('render');
    return (
    <Measure client onResize={this._handleResize}>
      {({ measureRef }) => {
        return (
          <div
            ref={(elem) => {
              console.log('measureRef');
              measureRef(elem);
            }}
          >
          stuff inside
          </div>
        );
      }}
      </Measure>
    );
  }

}
...

What I see is that for some reason which I don't get React is calling the ref callback every frame but it's NOT calling render. The ref it's passing (in the code above elem) is alternatively the <div> and then null on next callback repeating forever one than the other.

Measure is then disconnecting it's internal ResizeObserver and reconnecting . This causes the onResize handler to be called and then it all repeats next frame.

No state is changing (looked at all of it) , deleted or commented out the children, didn't help.

For the moment I solved the issue by only calling measureRef if the ref is not null and different than previous

              ref={(elem) => {
                  console.log('measureRef');
                  if (elem && this._lastElem !== elem) {
                    this._lastElem = elem;
                    measureRef(elem);
                  }
              }}

Now everything works as expected. I stop getting the repeated ref callbacks. Measure still seems to be doing it's job for me measuring everything I expect it to measure.

i'll keep trying to track down a smaller repo (so far smaller samples have not repoed) but if any one has any ideas where to look that would be helpful.

Ok, I managed to make a repo

https://codesandbox.io/s/4820o8rn47

The only difference between working and NOT working is whether or not I use a closure for the ref callback.

This works

        {({ measureRef }) =>
          <pre
            ref={measureRef}
          ></pre>

This does NOT work

        {({ measureRef }) =>
          <pre
            ref={(e) => {measureRef(e)}}
          ></pre>

Any ideas why that would cause an issue?

Unfortunately I also need the ref for my own code which is why I had a closure in there.

Hey @greggman thanks for filing an issue! This is on my radar. I'm currently working on a new version of react-measure that will solve this issue. I'll post a PR soon so you can try it out if you would like. It will be a fairly similar API with a lot more power and should fix most of the outstanding issues.

I did run into the same problem. Did someone find a solution/is this PR somewhere?

Same here, with current version 2.2.5 problem is not solved. What would be solution?

It's not the most elegant, but for now, you can do what was suggested above and check to make sure it's only called once:

ref={node => {
  if (!this.node) {
    measureRef(node)
    this.node = node
  }
}}

I'm hoping to hop back on this project soon and get a new release out with a hooks version, and issues like this solved better 😇. If you happen to come across a solution that mitigates this, please feel free to add a PR. Although, I think with the way refs attach and detach it may be unavoidable 🤔.

maybe I found a workaround.

const Foo = ({ measureRef }) => <p ref={useCallback((e) => measureRef(e), [measureRef])} />;
const App = (
  <Measure>
    {({ measureRef }) => (
      <Foo measureRef={measureRef} />
    )}
  </Measure>
);

https://reactjs.org/docs/refs-and-the-dom.html#caveats-with-callback-refs

↑Here, If the ref callback is defined as an inline function, it will get called twice during updates. and You can avoid this by defining the ref callback as a bound method on the class.

I think this is the cause of this problem. You shouldn't pass ref as inline function. You should memoize a function and pass it.