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

Delaying update of height and width in Layout #3180

Merged
merged 4 commits into from
Mar 11, 2018

Conversation

wuweiweiwu
Copy link
Member

@wuweiweiwu wuweiweiwu commented Mar 10, 2018

Issue:

What I did

fixes #1990
Delayed the setState call so we don't rerender a billion times.

Plus removed adding un-needed event handlers to window

How to test

use React inspector to check state updates

yarn start

resize the windows and see that the previewPanelDimensions state isn't updated thus doesn't rerender until after you stopped dragging for a little bit (50ms)

Is this testable with jest or storyshots?

No

Does this need a new example in the kitchen sink apps?

No

Does this need an update to the documentation?

No

@wuweiweiwu wuweiweiwu force-pushed the issue-1990 branch 2 times, most recently from cebb1d2 to 48c95cd Compare March 10, 2018 00:11
@codecov
Copy link

codecov bot commented Mar 10, 2018

Codecov Report

Merging #3180 into master will decrease coverage by 0.04%.
The diff coverage is 16.66%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3180      +/-   ##
==========================================
- Coverage   36.02%   35.98%   -0.05%     
==========================================
  Files         437      437              
  Lines        9465     9476      +11     
  Branches      903      905       +2     
==========================================
  Hits         3410     3410              
- Misses       5460     5470      +10     
- Partials      595      596       +1
Impacted Files Coverage Δ
lib/ui/src/modules/ui/components/layout/index.js 27.15% <16.66%> (-2.14%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7daa522...31e2a2b. Read the comment docs.

@@ -140,14 +140,6 @@ class Layout extends React.Component {
this.onDragEnd = this.onDragEnd.bind(this);
}

componentDidMount() {
window.addEventListener('resize', this.onResize);
Copy link
Member

Choose a reason for hiding this comment

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

are these not needed anymore?

I think we still need to handle the case when the browser window gets resized?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don’t think so. Because onResize returns another function the window resize events never actually updates the state.

I will have to double check but I think the SplitPane onChange handler fires when browser window is resizes.

Copy link
Member

Choose a reason for hiding this comment

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

Yes we need. Try to resize window here https://deploy-preview-3180--storybooks-official.netlify.com

Copy link
Member Author

@wuweiweiwu wuweiweiwu Mar 10, 2018

Choose a reason for hiding this comment

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

@Hypnosphi I also doubled checked on master resizing the browser window never triggered onResize

I'm not sure how we should handle browser window resizing since the Layout onChange handlers don't fire

Copy link
Member

@Hypnosphi Hypnosphi Mar 10, 2018

Choose a reason for hiding this comment

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

I also doubled checked on master resizing the browser window never triggered onResize

Oh I see. Looks like a bug worth fixing to me. Probably caused by onResize returning a function

Copy link
Member

Choose a reason for hiding this comment

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

We should probably extract the part that updates previewPanelDimensions from onResize, because it's the only thing that should be done on window resize

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds good!. I'll change that in this PR!

height: clientHeight,
},
});
}, 50);
Copy link
Member

Choose a reason for hiding this comment

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

Please use lodash.debounce package

Copy link
Member

Choose a reason for hiding this comment

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

Or maybe it should rather be lodash.throttle, see #3180 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

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

@Hypnosphi Sounds good. I discovered a new issue with using _.throttle the way that it is currently implemented. it will create a new throttled function on every state update which is bad. Thus I will be changing how the onResize function and how its passed in to the onChange handlers

Copy link
Member

@Hypnosphi Hypnosphi Mar 10, 2018

Choose a reason for hiding this comment

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

Just apply it in constructor

this.onResize = throttle(this.onResize.bind(this), 200);

Copy link
Member

Choose a reason for hiding this comment

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

I tend to use that method of writing bound class methods a lot.

Copy link
Member

@danielduan danielduan Mar 10, 2018

Choose a reason for hiding this comment

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

for readability purposes, it might be better to name it this.onThrottledResize = ... to differentiate between the two.

@Hypnosphi
Copy link
Member

I tried it and I can't say I like it:

https://deploy-preview-3180--storybooks-official.netlify.com/?selectedKind=ui%2FAddonPanel&selectedStory=default&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Fstories%2Fstories-panel

Dimensions panel is used for instant feedback, so picking debouncing over throttling doesn't look like a good choice to me

@ndelangen
Copy link
Member

Can we debounce on requestAnimationFrame?

@ndelangen
Copy link
Member

@wuweiweiw thank you for helping, really appreciate the efforts! I think the immediate feedback of the size is really important, if someone is targetting 800px wide for example, it's annoying to not yet fast feedback, and likely 'overshoot' multiple times.. Do you agree?

I think if we can throttle on requestAnimationFrame everything should be optimal right?

@danielduan
Copy link
Member

The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint.
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

From my understanding of these docs, it seems like this should be used when animating or whenever there's a constraint on browser rendering speed. In this case, I believe the setState updates are a constraint on CPU cycles so I'm not sure if this is an appropriate use.

I'm okay with either throttle or requestAnimationFrame. At this point, it seems like a micro optimization.

@Hypnosphi
Copy link
Member

Can we debounce on requestAnimationFrame?

It's only about updating numbers on dimension panel, no need for 60 fps animations

@wuweiweiwu
Copy link
Member Author

@Hypnosphi @danielduan @ndelangen done!

I rewrote the onResize function so it doesn't return a function. And thus a new function wouldn't be created in render() every time. The _.throttle is set to 200ms but that can be changed :)

import PropTypes from 'prop-types';
import React from 'react';
import SplitPane from 'react-split-pane';
import _ from 'lodash'; /* eslint-disable-line import/no-extraneous-dependencies */
Copy link
Member

Choose a reason for hiding this comment

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

We use per-function lodash packages. You need to add lodash.throttle as a dependency to package.json in lib/ui, and import it like that:

import throttle from 'lodash.throttle';

By the way, that eslint warning you've suppressed was there for a reason. If you use a package that we don't have as a direct or peer dependency, it will break on user side, because they might not have this dependency

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed! :)

Copy link
Member

Choose a reason for hiding this comment

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

Is it? I still see this import and eslint-disable

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh shoot. I think I accidentally dropped that commit when I was rebasing

@Hypnosphi
Copy link
Member

Hypnosphi commented Mar 10, 2018

And thus a new function wouldn't be created in render() every time

Actually, it is created every time, now it's just created directly in render (the arrow function)

@wuweiweiwu
Copy link
Member Author

wuweiweiwu commented Mar 10, 2018

@Hypnosphi My mistake. But I just got rid of the way onResize was returning the function that took in size and moved it inside render. I think that makes more sense

And if we put throttle on the inner function returned. It would be executed every time onSize was called

@Hypnosphi
Copy link
Member

The way it was before is more functional-style, the way you do it is more imperative-style. I have no strong feelings about it though

@wuweiweiwu wuweiweiwu force-pushed the issue-1990 branch 2 times, most recently from 0b27b6f to 65329d3 Compare March 11, 2018 12:05
@wuweiweiwu
Copy link
Member Author

@Hypnosphi I also added the throttled event handler to window resize events.

Is there anything else I should do in this PR?

onResize(pane, mode) {
return size => {
onResize(pane, mode, size) {
if (pane && mode && size) {
Copy link
Member

@Hypnosphi Hypnosphi Mar 11, 2018

Choose a reason for hiding this comment

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

Looks a bit hacky to me. Let's extract the part that updates state into a separate function instead.

In fact, the state updates should be throttled (because they provide visual feedback), and the saveSizes part should be debounced (because it only makes sense to store sizes when they are final)

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds good!

Copy link
Member Author

@wuweiweiwu wuweiweiwu Mar 11, 2018

Choose a reason for hiding this comment

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

@Hypnosphi seems that size is used to get defaultSize and that value is only changed when you save it to local storage. Thus I was getting some weird behavior when I was debouncing. So I opted to throttle both.

Copy link
Member Author

Choose a reason for hiding this comment

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

I also made saveSize on a smaller interval for smoother user experience

@wuweiweiwu
Copy link
Member Author

@Hypnosphi Fixed! I think we can even get rid of the throttling of saveSize but I'm not sure how expensive it is to save to localStorage

@wuweiweiwu
Copy link
Member Author

And also it seems we can call getSizes in componentDidMount and save the current size in local state. And then we can use lodash.debounce to update the size in localStorage
Thus we would only access localStorage when we actually need to instead of fetching on every render.

But I think that is out of scope for this PR and I can do it in another :)

@Hypnosphi
Copy link
Member

Cool, works like a charm now!

@Hypnosphi Hypnosphi merged commit 123a9c0 into storybookjs:master Mar 11, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants