Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Understanding how measureRef works #90

Open
greggman opened this issue Feb 15, 2018 · 6 comments
Open

Understanding how measureRef works #90

greggman opened this issue Feb 15, 2018 · 6 comments

Comments

@greggman
Copy link

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.

@greggman
Copy link
Author

greggman commented Feb 15, 2018

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.

@souporserious
Copy link
Owner

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.

@ksjogo
Copy link

ksjogo commented Mar 11, 2019

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

@markoristic3ap
Copy link

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

@souporserious
Copy link
Owner

souporserious commented Apr 3, 2019

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 🤔.

@pandanoir
Copy link

pandanoir commented Feb 14, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants