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

[Core] Rerender popovers when content resizes #2718

Merged
merged 11 commits into from
Jul 24, 2018
Merged

[Core] Rerender popovers when content resizes #2718

merged 11 commits into from
Jul 24, 2018

Conversation

badams
Copy link
Contributor

@badams badams commented Jul 24, 2018

Fixes #2684 / fixes #692

Changes proposed in this pull request:

Proposed solution is to implement ResizeObserver as mentioned here to monitor for resizes on this.popoverElement.

  • I found the performance severely impacted if blindly creating an observer on every call to Popover.renderPopover, so instead initialise the observer on mount and then update reference to the Popper.scheduleUpdate on every call to Popover.renderPopover instead.

  • Realised that the DOM node the observer is watching may get removed when the popover is unmounted, so it would stop working, decided to update this on every componentDidUpdate which seems to work much better.

  • I am unsure how to implement tests for this change, but if they are required I can spend more time looking into this.

Screenshot

Before

Alt Text

After

Alt Text

Copy link
Contributor

@giladgray giladgray left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome! thank you @badams! a few comments.


if (this.popoverElement instanceof HTMLElement) {
// Ensure our observer has an up-to-date reference to popoverElement
this.popperObserver.observe(this.popoverElement);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clear the observer before observing new elements. this will quickly result in a long list of watched elements, many of which may not be in the DOM.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@giladgray
Unfortunately the ResizeObserver spec doesn't allow removing observers if you don't have a reference to the target.

I've opted to just recreate the observer completely, does this seem reasonable you?

@@ -201,6 +208,7 @@ export class Popover extends AbstractPureComponent<IPopoverProps, IPopoverState>

public componentDidMount() {
this.updateDarkParent();
this.popperObserver = new ResizeObserver(() => this.popperUpdater && this.popperUpdater());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add this.popperObserver.disconnect() in componentWillUnmount

private popperObserver: ResizeObserver;

// Reference to the Poppper.scheduleUpdate() function, this changes every time the popper is mounted
private popperUpdater: () => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

popperScheduleUpdate? schedulePopperUpdate? make the name obvious.

@@ -288,6 +301,8 @@ export class Popover extends AbstractPureComponent<IPopoverProps, IPopoverState>
this.props.popoverClassName,
);

this.popperUpdater = popperProps.scheduleUpdate;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do this as the first line in this method rather than breaking up this rendering logic, with a // comment about storing it on instance because it can change on every render.

@badams
Copy link
Contributor Author

badams commented Jul 24, 2018

Thanks for taking the time to review this @giladgray, have implemented your suggestions with the one exception, please take a look at my comments

this.popperObserver.disconnect();
}

this.popperObserver = new ResizeObserver(() => this.popperScheduleUpdate && this.popperScheduleUpdate());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems to me that calling disconnect() should be enough, no need to recreate: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-disconnect

  1. Clear the observationTargets list.

  2. Clear the activeTargets list.

please revert this so it's only created once and simply invoke disconnect before observing the new one.

private renderPopover = (popperProps: PopperChildrenProps) => {
const { usePortal, interactionKind } = this.props;
const { transformOrigin } = this.state;

// Need to update our reference to this on every render as the target node may have changed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not the target node but the function instance itself!

Copy link
Contributor

@giladgray giladgray left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final requests!

@@ -201,6 +208,7 @@ export class Popover extends AbstractPureComponent<IPopoverProps, IPopoverState>

public componentDidMount() {
this.updateDarkParent();
this.popperObserver = new ResizeObserver(() => this.popperScheduleUpdate && this.popperScheduleUpdate());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

finally, let's be typesafe and avoid coercion. use our safeInvoke util here.

new ResizeObserver(() => safeInvoke(this.popperScheduleUpdate))

@@ -221,10 +229,19 @@ export class Popover extends AbstractPureComponent<IPopoverProps, IPopoverState>

public componentDidUpdate() {
this.updateDarkParent();

if (this.popoverElement instanceof HTMLElement) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

our convention for this check on refs is != null, no need to repeat the type.

@giladgray
Copy link
Contributor

preview

Copy link
Contributor

@giladgray giladgray left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alright this looks good!! thanks @badams 👏 👏

@giladgray giladgray merged commit 5abe71e into palantir:develop Jul 24, 2018
@badams
Copy link
Contributor Author

badams commented Jul 24, 2018

Awesome! thanks @giladgray looking forward to 3.1! 👍

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

Successfully merging this pull request may close these issues.

Multiselect Popover with expanding input FR: Popovers should allow changing size
2 participants