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

Guard against matchMedia access during initial render #1459

Merged
merged 2 commits into from
Jul 30, 2024

Conversation

somewhatabstract
Copy link
Member

Summary:

This PR makes sure that we don't call matchMedia during the initial render. This is because matchMedia is not available during the initial render, and calling it will throw an error.

Issue: FEI-5743

Test plan:

  1. Put up this PR and await the pre-release packages
  2. Update webapp with the pre-release packages
  3. Put up a webapp PR and check that the ZND is working on URLs that were previously erroring

@somewhatabstract somewhatabstract self-assigned this Jul 29, 2024
@somewhatabstract somewhatabstract requested review from a team July 29, 2024 21:47
@khan-actions-bot
Copy link
Contributor

khan-actions-bot commented Jul 29, 2024

Gerald

Required Reviewers
  • @Khan/perseus for changes to .changeset/quiet-moose-arrive.md, packages/perseus/src/widgets/explanation.tsx, packages/perseus/src/widgets/label-image.tsx, packages/perseus/src/widgets/label-image/marker.tsx

Don't want to be involved in this pull request? Comment #removeme and we won't notify you of further changes.

Copy link
Contributor

github-actions bot commented Jul 29, 2024

npm Snapshot: Published

Good news!! We've packaged up the latest commit from this PR (cea126a) and published it to npm. You
can install it using the tag PR1459.

Example:

yarn add @khanacademy/perseus@PR1459

If you are working in Khan Academy's webapp, you can run:

./dev/tools/bump_perseus_version.sh -t PR1459

Comment on lines +98 to +99
this._mounted &&
mediaQueryIsMatched("(prefers-reduced-motion: no-preference)");
Copy link
Member Author

Choose a reason for hiding this comment

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

note: We don't want animation on by default on initial render in case the user has turned it off (long term, it would be useful if this also could be overridden by webapp profile setting too).

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm confused how this fixes things. The mediaQueryIsMatched function guards against window.matchMedia not being a function. Is that helper written wrong or is the fix more about only running on a mounted component?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a subtle thing, but we don't just want to guard against it being accessed; the initial render on both server and client must be identical, so we don't want to use matchMedia at all on first render as the client could give a different rendered result than the server.

@@ -106,7 +119,7 @@ export default class Marker extends React.Component<Props> {
} else if (showPulsate) {
iconStyles = [
styles.markerPulsateBase,
shouldReduceMotion()
this._mounted && shouldReduceMotion()
Copy link
Member Author

Choose a reason for hiding this comment

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

note: We don't want animation on by default on initial render in case the user has turned it off (long term, it would be useful if this also could be overridden by webapp profile setting too).

const isWideImage =
this.props.imageWidth / 2 > this.props.imageHeight;
// Determine whether the image is wider than it is tall.
const isWideImage = this.props.imageWidth / 2 > this.props.imageHeight;
Copy link
Member Author

Choose a reason for hiding this comment

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

note: Moved out of the loop; doesn't need recalculating multiple times.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for fixing this up!

Comment on lines +517 to +519
this._mounted &&
window.matchMedia(mediaQueries.xsOrSmaller.replace("@media ", ""))
.matches;
Copy link
Member Author

Choose a reason for hiding this comment

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

note: Moved out of the loop since it won't change for each item, and modified to return false on initial render so that it will SSR and hydrate correctly.

Copy link
Contributor

github-actions bot commented Jul 29, 2024

Size Change: +86 B (+0.01%)

Total Size: 849 kB

Filename Size Change
packages/perseus/dist/es/index.js 411 kB +86 B (+0.02%)
ℹ️ View Unchanged
Filename Size
packages/kas/dist/es/index.js 38.2 kB
packages/kmath/dist/es/index.js 4.27 kB
packages/math-input/dist/es/index.js 80.6 kB
packages/math-input/dist/es/strings.js 1.73 kB
packages/perseus-core/dist/es/index.js 1.48 kB
packages/perseus-editor/dist/es/index.js 270 kB
packages/perseus-linter/dist/es/index.js 21.6 kB
packages/perseus/dist/es/strings.js 3.21 kB
packages/pure-markdown/dist/es/index.js 3.67 kB
packages/simple-markdown/dist/es/index.js 12.4 kB

compressed-size-action

Copy link

codecov bot commented Jul 29, 2024

Codecov Report

Attention: Patch coverage is 79.54545% with 9 lines in your changes missing coverage. Please review.

Project coverage is 70.12%. Comparing base (9bc4812) to head (cea126a).
Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1459      +/-   ##
==========================================
+ Coverage   69.79%   70.12%   +0.33%     
==========================================
  Files         505      508       +3     
  Lines      105003   104905      -98     
  Branches     7548    10713    +3165     
==========================================
+ Hits        73283    73565     +282     
+ Misses      31536    31340     -196     
+ Partials      184        0     -184     

Impacted file tree graph

Files Coverage Δ
packages/perseus/src/widgets/explanation.tsx 93.44% <91.66%> (+1.52%) ⬆️
packages/perseus/src/widgets/label-image.tsx 83.11% <83.33%> (-0.06%) ⬇️
...ackages/perseus/src/widgets/label-image/marker.tsx 90.24% <64.28%> (-1.37%) ⬇️

... and 151 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

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

Copy link
Member

@jeresig jeresig left a comment

Choose a reason for hiding this comment

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

These changes all seem reasonable to me, thank you for figuring this out, Jeff! I'll defer to the Perseus team for final approval here, though.

Copy link
Collaborator

@jeremywiebe jeremywiebe left a comment

Choose a reason for hiding this comment

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

The fixes mostly make sense. I left one question and I think it would make sense to move the mediaQueryIsMatched helper to a new file in packages/perseus/src/util. Not a deal-breaker, but if you decide not to please let me know and I'll create a ticket to do this after the fact.

Thanks for chasing this down Jeff.

Comment on lines +98 to +99
this._mounted &&
mediaQueryIsMatched("(prefers-reduced-motion: no-preference)");
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm confused how this fixes things. The mediaQueryIsMatched function guards against window.matchMedia not being a function. Is that helper written wrong or is the fix more about only running on a mounted component?

// Determine whether page is rendered in a narrow browser window.
const isNarrowPage =
this._mounted &&
window.matchMedia(mediaQueries.xsOrSmaller.replace("@media ", ""))
Copy link
Collaborator

Choose a reason for hiding this comment

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

This usage doesn't use a helper to guard window.matchMedia being undefined. I wonder if we should move all these checks to call a helper that guards usage properly. Would that alleviate us needing the _mounted flag in each component?

Copy link
Member Author

@somewhatabstract somewhatabstract Jul 30, 2024

Choose a reason for hiding this comment

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

Guarding against it being undefined is really a red herring; I think perhaps that check should be removed where we have it. I don't think we support any browsers that won't have it at this point, and we shouldn't be determining by that measure if we call it or not on initial render as we need that to match on both client (where it exists) and server (where it doesn't exist or might give a different response than on the client).

The _mounted flag is the important bit to avoid having a different initial render on the client than on the server (both initial renders have to match or hydration fails)

@somewhatabstract somewhatabstract merged commit be40d77 into main Jul 30, 2024
11 checks passed
@somewhatabstract somewhatabstract deleted the matchMediaFix branch July 30, 2024 15:33
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.

4 participants