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

Move responsive sizes to the code-studio Redux store #18861

Merged
merged 7 commits into from
Nov 6, 2017

Conversation

Bjvanminnen
Copy link
Contributor

No description provided.

};

export function getResponsiveBreakpoint() {
const width = window.innerWidth;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would let caller provide width rather than introduce a dependency on window here.

const store = getStore();

window.addEventListener('resize', debounce(() => {
store.dispatch(setResponsiveSize());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should be providing a param, right? Should we actually be providing the window width vs. the responsiveSize breakpoint? (possible im misunderstanding something)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, right, I see what you're saying. I'll move the logic out of the reducer and into this file.

For some reason I thought I should provide initial state via getResponsiveBreakpoint and moved the function definition to the reducer. Instead, I think response.js calls setResponsiveSize() immediately with the initial size category (lg/md/sm/xs) in addition to setting up the event listener. Does that sound right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I was maybe thinking about this all wrong. Let me start fresh and state some assumptions about what we're trying to accomplish to see if they are right:

  • Goal: We want to be able to easily tell how big the current window is, but bucketed into a few different categories - i.e. we want to know if it is sx, sm, md, or lg.

If true, this is what I would do:

  1. Provide a setWindowSizeAction
  2. Have an eventListener that dispatches setWindowSizeAction every time we resize (possibly/probably debounced).
  3. Have a reducer that listens for the setWindowSize action, and returns the breakpoint (xs, sm, etc.) for that size.

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes sense! Any reason to have the conversion of width in px to lg/md/sm/xs live in the reducer vs. in responsive.js?

I guess what I'm asking is whether setWindowSizeAction should take a px width of the exact window size or one of lg/md/sm/xs. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alternatively, we could store the raw window size, and provide a helper so that instead of something like

connect(state => {
  breakpoint: state.responsiveBreakpoint
})

we would do

connect(state => {
  breakpoint: responsiveBreakpoint(state.windowWidth)
}

Just storing the breakpoint is maybe easier to consume.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think either way works (passing in breakpoint or size)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Having the conversion happen in the reducer perhaps make it easier to test.


/**
* Listen for page resize and dispatch events to Redux when if cross a
* responsive breakpoint width.
Copy link
Member

Choose a reason for hiding this comment

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

Note that layouts don't only re-render when we cross a breakpoint width. Many of them resize to 100% of current width, so they need to re-render whenever the window changes size (though debouncing helps reduce intensity).

Copy link
Contributor

Choose a reason for hiding this comment

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

Can this happen with CSS instead of absolute sizing?

Copy link
Contributor

Choose a reason for hiding this comment

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

Likewise with the course cards I'm testing this out with: can these have float: left; and let CSS sort out when to reflow them to the next line, based on the available width?

Maybe I'm not understanding how the React components are laid out on the page.

Copy link
Member

Choose a reason for hiding this comment

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

As we discussed elsewhere, I retract my initial statement for now. We do have pages whose layout changes in various ways between thresholds, but all of that is CSS rules being applied (often width: 100% in particular) rather than DOM changes occurring. So, as you've pursued, it does make sense as an optimisation to only force the re-render (through changing state or otherwise) when we cross a threshold. That said, we've survived with a debounced re-render of the DOM upon all resizes in /learn for the past year without known complaint.

As you pointed out, if we did start writing code that depended on values like current window width, then we'd want to re-render the DOM more often than just when crossing the threshold. But afaik, we haven't done that yet.

@joshlory
Copy link
Contributor

joshlory commented Nov 3, 2017

Ok, I think this is ready for a first pass @islemaster @caleybrock. Thanks!

@joshlory
Copy link
Contributor

joshlory commented Nov 3, 2017

My plan is to update the remaining consumers of new Responsive() in a separate PR. Let me know if you'd like to see everything in one PR.

Copy link
Contributor

@islemaster islemaster left a comment

Choose a reason for hiding this comment

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

It's lovely!


window.addEventListener('resize', debounce(() => {
store.dispatch(setResponsiveSize(getResponsiveBreakpoint(window.innerWidth)));
}));
Copy link
Contributor

Choose a reason for hiding this comment

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

Two suggestions here:

  1. Using the default wait value of 0 (only waiting until the next tick) may not improve performance enough for lower-end machines. I'd suggest a value of at least 50ms (<=20FPS) - especially not knowing how much behavior we'll attach to this event down the road.

  2. I might use _.throttle() instead of debounce here. With throttle, a very smooth window resize would still trigger intermediate re-renders, albeit at whatever reduced rate we choose.

export function getResponsiveBreakpoint(width) {
return Breakpoints.find(({breakpoint}) => {
if (width > breakpoint) {
return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Collapse the if (condition) return true pattern:

return Breakpoints
  .find(({breakpoint}) => width > breakpoint)
  .responsiveSize;

@caleybrock
Copy link
Contributor

This is great, I'm excited to have something consistent to use. 👍

export const ResponsiveSize = makeEnum('lg', 'md', 'sm', 'xs');

// Default window widths that are the starting points for each width category.
const Breakpoints = [
Copy link
Contributor

Choose a reason for hiding this comment

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

Has any further thought gone into these widths? I remember @breville choosing ones that worked well for the page he worked on. 992 matches bootstrap, but I'm not sure where the other ones come from: https://v4-alpha.getbootstrap.com/layout/overview/#responsive-breakpoints

Copy link
Contributor

@caleybrock caleybrock Nov 6, 2017

Choose a reason for hiding this comment

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

Since our page size width is 970 I'd expect one of the breakpoints to be 972 or something?

@caleybrock
Copy link
Contributor

Does this mean we can replace/delete either of these: https://github.com/code-dot-org/code-dot-org/blob/staging/apps/src/responsive.jsx
https://github.com/code-dot-org/code-dot-org/blob/staging/apps/src/tutorialExplorer/responsive.jsx

It'd be great to not have 3 versions of this responsive work in our code base. There is already confusion about what to use because PLC tools uses bootstrap to make some of their react responsive.

@joshlory
Copy link
Contributor

joshlory commented Nov 6, 2017

@caleybrock yep! My plan is to do that in a separate PR, see #18861 (comment). But I can combine everything into one PR if you prefer.

@caleybrock
Copy link
Contributor

It's fine to separate if it's a quick followup. 👍

@joshlory joshlory merged commit 35d56ec into staging Nov 6, 2017
@joshlory joshlory deleted the responsive-page-redux branch November 6, 2017 22:37
@breville
Copy link
Member

Sorry for delay on this. Two questions:

  1. Can a component still access the various helper methods from https://github.com/code-dot-org/code-dot-org/blob/staging/apps/src/responsive.jsx such as isResponsiveCategoryActive, isResponsiveCategoryInactive, and getResponsiveValue?
  2. Is there a migration pathway in which legacy components can continue using legacy responsive system even as other components on the page use this new system?

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.

None yet

5 participants