Page MenuHomePhabricator

[landing] fix mobile nav still being shown when window resizes
ClosedPublic

Authored by ginsu on May 19 2023, 5:03 AM.
Tags
None
Referenced Files
F1430822: D7870.id.diff
Wed, Mar 27, 11:54 PM
Unknown Object (File)
Tue, Mar 12, 3:50 PM
Unknown Object (File)
Wed, Mar 6, 9:48 AM
Unknown Object (File)
Mon, Mar 4, 12:36 AM
Unknown Object (File)
Mon, Mar 4, 12:36 AM
Unknown Object (File)
Feb 10 2024, 4:14 PM
Unknown Object (File)
Feb 10 2024, 4:13 PM
Unknown Object (File)
Feb 10 2024, 4:13 PM

Details

Summary

As I was doing some tests on the new Header I noticed that if a user resized the window while the mobile nav header was acitve, the moible nav header would still be open. This diff fixes this problem by adding a resize event listener that checks if the window width is more than 848px (the breakpoint that @ted and I determined earlier) and if the window is ever bigger than 848px sets the showMobileNav state to false

Depends on D7869

Test Plan

Please watch the demo videos below:

Before:

After:

Diff Detail

Repository
rCOMM Comm
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

ginsu requested review of this revision.May 19 2023, 5:21 AM
landing/landing.react.js
56 ↗(On Diff #26667)

This is very brittle and hard to maintain... it's likely something changes with the breakpoint, and nobody will know that this code needs to be updated.

Can this constant be defined somewhere and given a name, along with a comment that explains where it comes from (presumably some CSS file), and a comment in the CSS file indicating that the value must be kept in sync here?

atul requested changes to this revision.May 19 2023, 10:53 AM

Maybe I'm missing something, but could we just have the MobileNav set to display: none at higher breakpoint?

This revision now requires changes to proceed.May 19 2023, 10:53 AM

Maybe I'm missing something, but could we just have the MobileNav set to display: none at higher breakpoint?

We could do this and that would get rid of the MobileNav when the window resizes to a bigger width; however, the issue with this solution is that when we resize back to a smaller window width again, then the MobileNav would be immediately displayed again since we never set showMobileNav to false. I thought this was not a very elegant solution and was a weird user experience so I opted not to do it.

If we want to go this direction, I can make the necessary changes, but for now I will go with my current solution

ginsu edited the test plan for this revision. (Show Details)

address ashoats comments

the issue with this solution is that when we resize back to a smaller window width again, then the MobileNav would be immediately displayed again

Checked a couple websites w/ similar pattern (eg apple.com) and it looks they all hide the equivalent of MobileNav when webpage is sized back down. Not sure how they implement, but looks like what you're doing is consistent..

landing/header.css
119 ↗(On Diff #26724)

Could we be more specific than "this value"?

Guessing you're referring to the max-width value?

landing/header.react.js
21 ↗(On Diff #26724)

We'll sometime do something like

export const HEADER_BREAKPOINT = 848; // px

to denote units.

Usually in the context of time I think, but wouldn't hurt to include here?

landing/landing.react.js
55–59 ↗(On Diff #26724)

Do we need to define this function within the useEffect(...)?

This revision is now accepted and ready to land.May 22 2023, 9:19 AM
landing/landing.react.js
55–59 ↗(On Diff #26724)

We want to make sure that we use showMobileNav directly in the useEffect to ensure that the most current state value of showMobileNav is being used. If we put the showMobileNav in the callback we could run into the situation where the handleResize function created in a particular render will always refer to the showMobileNav value from that specific render, and this could be an outdated value. With this in mind we could change the effect to look like this

const handleResize = React.useCallback(() => {
  if (window.innerWidth > HEADER_BREAKPOINT) {
    setShowMobileNav(false);
  }
}, [setShowMobileNav]);

React.useEffect(() => {
  if (showMobileNav) {
    window.addEventListener('resize', handleResize);
  }

  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, [handleResize, showMobileNav]);

However with this solution we may be removing an event listener even when it's added. Technically there is no harm/risk in doing so, but it is a bit strange and considered an antipattern.

The last comment was pretty concerning...

landing/landing.react.js
55–59 ↗(On Diff #26724)

If we put the showMobileNav in the callback we could run into the situation where the handleResize function created in a particular render will always refer to the showMobileNav value from that specific render, and this could be an outdated value.

Pretty sure you have the exact same problem in your current implementation? You're not declaring any refs, so my intuition tells me there can't be any difference in approaches. Your handleResize function is going to be bound with the value of showMobileNav at the time the effect was run.

This strongly smells like a core misunderstanding, not just about React but about how variables work in JavaScript.

However with this solution we may be removing an event listener even when it's added. Technically there is no harm/risk in doing so, but it is a bit strange and considered an antipattern.

I don't understand why you think you have to do that. Can't you just do this?

const handleResize = React.useCallback(() => {
  if (window.innerWidth > HEADER_BREAKPOINT) {
    setShowMobileNav(false);
  }
}, [setShowMobileNav]);

React.useEffect(() => {
  if (showMobileNav) {
    window.addEventListener('resize', handleResize);
    return undefined;
  }

  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, [handleResize, showMobileNav]);

Oops – this is what I should've said (had a typo in my last comment):

const handleResize = React.useCallback(() => {
  if (window.innerWidth > HEADER_BREAKPOINT) {
    setShowMobileNav(false);
  }
}, [setShowMobileNav]);

React.useEffect(() => {
  if (!showMobileNav) {
    return undefined;
  }

  window.addEventListener('resize', handleResize);
  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, [handleResize, showMobileNav]);