-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Fire 'onLayout' when elements are resized #848
Conversation
} | ||
); | ||
|
||
Component.prototype.componentDidUpdate = safeOverride( | ||
componentDidUpdate, | ||
function componentDidUpdate() { | ||
this._handleLayout(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually removing this might break backwards compatibility. Happy to fix this if you think it is necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
Thanks for taking this on! One question I had when I looked at resize observer is whether the contentRect/measure dimensions are equivalent when the node has padding or border. Do you know? |
The
So for example if we have {
x: 10,
y: 10,
top: 10,
left: 10,
right: 310,
bottom: 210,
width: 300,
height: 200
} I am not familiar with the Unrelated: the calls to |
if it's returning the layout of the contentRect and not the bounding box, the resize observer path might be returning different results. Agree that debouncing could be removed in this patch |
just to make sure I understood
Are you saying that we need the bounding box (haven't had the chance to check the implementation of |
Yeah I'd make a bunch of different boxes with/without padding, border, etc., and compare the results you get in snack.expo.io with the web implementations to make sure it is the same in every case (inc when offset in a scroll view) |
Indeed using the Since the |
const safeOverride = (original, next) => { | ||
if (original) { | ||
return function prototypeOverride() { | ||
original.call(this); | ||
next.call(this); | ||
/* eslint-disable prefer-rest-params */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
personally I think that it is unnecessary to use the rest operator in this case, this version should perform better than its transpiled equivalent.
<AppText> | ||
Invoked on mount and layout changes with{' '} | ||
<Code>{'{ nativeEvent: { layout: { x, y, width, height } } }'}</Code>, where{' '} | ||
<Code>x</Code> and <Code>y</Code> are the offsets from the parent node. | ||
</AppText>, | ||
<AppText> | ||
NOTE: Behind the hood React Native for Web uses <Code>ResizeObserver</Code> and doesn't |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please could you include ResizeObserver
and a link to the polyfill in the list of recommended polyfills in the "Getting started" docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do
656a96a
to
9db10f1
Compare
|
||
### ResizeObserver | ||
|
||
To observe layout changes and fire [`onLayout`](https://facebook.github.io/react-native/docs/view.html#onlayout) when elements are resized, React Native for Web uses the [`ResizeObserver` API](https://wicg.github.io/ResizeObserver/). Browsers that don't support this API require a [polyfill](https://github.com/que-etc/resize-observer-polyfill) for this feature. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not a native speaker so there might be errors. Feel free to edit this in line in case you spot any :)
const observe = instance => { | ||
if (resizeObserver) { | ||
const node = findNodeHandle(instance); | ||
node._handleLayout = debounce(instance._handleLayout.bind(instance)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm applying this locally and noticed this. It would be good if we could avoid binding a new function to the DOM node and instead use a pointer (like _onLayoutId
) to the instance's method. Did you try that? Also wondering why this function is debounced.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, node._onLayoutId
and look up the instance in the registry
should work.
RE: debouncing
As I said above
Since the measure method you implemented takes this into account already, I decided to use ResizeObserver only to trigger instance._handleLayout calls. For this reason I am debouncing this function now because it triggers a new measurement which can be expensive (correct me if I am wrong here or think that we can or should not debounce).
Measurements will be triggered, whether the user callback is debunced/throttled or not. If we decide to get rid of the debouncing I think that, for the sake of consistency, we should also remove the debouncing here https://github.com/necolas/react-native-web/pull/848/files#diff-fa9d0b1cb86f4e38c30f6685ec719be4R44
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not debouncing measurements but the frequency with which onresize is called
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right but as a consequence of that measurements (which happen in _handleLayout
) will be delayed to when resize ends.
Also wondering why this function is debounced
That was to keep the behavior consistent otherwise, on resize, ResizeObserver would fire continuously and we'd invoke _handleLayout
(measure) without delays.
Let me know if I am missing something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I see. I wonder if it's helpful to debounce at all. I suspect most viewport resizing is discrete - orientation change, etc - rather than the kind of continuous resizing developers might do. And maybe debouncing is better left to app developers who know when they want to use it? There's also the fact that measurement uses rAF, which needs to change too.
I'm working on a branch for FlatList that I'll push up so you can see how this patch interacts with it. I do want to get this in, it's just surfacing a few existing issues related to layout measurement in the lib :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And maybe debouncing is better left to app developers who know when they want to use it?
Yes, that would be ideal. With the current implementation though they could only debounce onLayout
(the fn prop), measurements would happen regardless. https://github.com/giuseppeg/react-native-web/blob/9db10f12c4dd23c46adb3f33f86932fd57f1f33a/packages/react-native-web/src/modules/applyLayout/index.js#L124-L132
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true. But probably ok. What I'll do is create a feature branch for this block of work, apply a version of this patch, and then we can work out these other details from there. Thanks for taking the time to understand (and help me understand) the details and for being patient!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem! Sounds good, I pushed the change you suggested in #848 (comment) I didn't bother rebasing but I can do that if you want.
Thanks, this is now in master |
I asked Twitter to try this out in Twitter Lite and it produced the following error in Chrome for Android
|
Sounds like the issue I had when I tried nested Views in Expo. Unfortunately I don't have the gist anymore. Can you ask them to try the same or similar hierarchy on Expo/React Native? I am afraid that nested views can trigger infinite |
Please keep in mind that I don't have experience with RN so I might as well have done something wrong. |
Would a pause and resume approach work/fix the issue? We could call |
Maybe. We kind of need an example case that triggers the error to iterate against. I only know it's an issue because @paularmstrong saw the error triggered from a build of Twitter Lite using the version of RNW that's in master. |
I made a testcase https://snack.expo.io/@giu/onlayout-test clicking on "trigger resize" will trigger an infinite loop of updates in cascade (in RN too). The only difference with Web (ResizeObserver) is that it is never interrupted nor throws an error. |
Fixes #60
Uses
ResizeObserver
and fallbacks towindow.onresize
(with initial firing). This should be a backwards compatible change.I can add some integration tests if you want and think that the PR is good.