Material-ui: [useMediaQuery] always returns false at the first call

Created on 22 May 2020  路  13Comments  路  Source: mui-org/material-ui

Duplicate of #21048 - [useMediaQuery] always returns false first at page-load

  • [x] The issue is present in the latest release.
  • [x] I have searched the issues of this repository and believe that this is not a duplicate.

Current Behavior 馃槸

Well, the current behavior is clearly documented

options.noSsr (Boolean [optional]): Defaults to false. In order to perform the server-side rendering reconciliation, it needs to render twice. A first time with nothing and a second time with the children. This double pass rendering cycle comes with a drawback. It's slower. You can set this flag to true if you are not doing server-side rendering.

However the current behavior is incorrect, and working NOT as documented

Just for context - here is useMediaQuery source - https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/useMediaQuery/useMediaQuery.js#L44

  const [match, setMatch] = React.useState(() => {
    if (noSsr && supportMatchMedia) {
      // unless noSsr is set, this line would be NEVER called
      return matchMedia(query).matches;
    }
    if (ssrMatchMedia) {
      return ssrMatchMedia(query).matches;
    }
    return defaultMatches;
  });

From this code it's clear that __without noSsr set the match would be always defaultMatches__ at the first hook call.

While the intention was to keep it default (or ssrMatchMedia) only during hydrate. As a result even CSR only application sufferes from double rendering, which never ends while application is working.

Expected Behavior 馃

Expected behaviour - useMedia should set correct state just after application start.

  const [match, setMatch] = React.useState(() => {
    if ( (noSsr || pastHydration) && supportMatchMedia) {
      return matchMedia(query).matches;
    }
   ...
  });

However there is a problem of tracking that event, especially with partial hydration in mind, when such event could occur more that once. (not sure MUI supports it)

Technically speaking no change is required to useMediaQuery, but another functionality to provide correct value for noSsr is needed.

enhancement good first issue useMediaQuery

Most helpful comment

Hi there! I'm encountering this behavior. I'm not using SSR so I don't really understand how it influences this, but I'm having useMediaQuery return false in the initial call which results in a nasty flash of CSS. A result of true means mobile, so the page is loading initially with the desktop styles (false), then it instantly switches to the mobile styles). Am I doing something wrong, should I be setting some kind of parameter? Thanks!

All 13 comments

@theKashey We used to have a pastHydration like a flag and decided to remove it in #18670 & #18683. Is there any other improvement opportunity?

Your expected behavior would result in a hydration mismatch. Unless react core allows us to determine whether we're hydrating or not, we can't implement it correctly. Every isHydrating-like implementation I've seen is broken one way or another.

If you know what you're doing and are ware of the issues of isHydrating-like flags you can always do useMediaQuery(query, { noSsr: !isRenderingOnTheServer && !isHydrating })

I'd rather cause a second render pass than create a broken UI during hydration.

That's understandable. However why

  • hydrationCompleted was tracked in useMediaQuery
  • was a global variable

From my understanding meta information about "hydration" shall be stored in Context API, like here or here, shall be false by default and changed to true after, well, hydration is complete. But after hydration is complete - it shall be known for all underlaying components.
That would allow also partial hydration/multiple root cases in no time, but __requires additional Provider__ around all consumers, which could be a problem.

Frankly speaking - the existence of such Provider for such information is what I was looking for. Is there a place(Theme?) you can "safely" add one more Provider without breaking users?

I could add it manually to my own code, and wire directly to useMediaQuery, not a big problem, but right now MUI by default is providing an incorrect behavior for client-side only applications, as well as sub navigation, which is a core idea behind SPAs.

Going back to our user-survey, 70% of the React developers (at least the once we can reach) do client-side work only. I think that we can do a better job at documenting this noSsr option.

@theKashey What do you think of the following? Would this new section in the documentation solve your issue?

`diff diff --git a/docs/src/pages/components/use-media-query/use-media-query.md b/docs/src/pages/components/use-media-query/use-media-query.md index ca29e673c..59e671564 100644 --- a/docs/src/pages/components/use-media-query/use-media-query.md +++ b/docs/src/pages/components/use-media-query/use-media-query.md @@ -85,6 +85,29 @@ describe('MyTests', () => { });

+## Client-side only rendering
+
+In order to perform the server-side reconciliation, the hook needs to render twice.
+A first time with false and a second time with the resolved value.
+This double pass rendering cycle comes with a drawback. It's slower.
+You can set the noSsr option to true if you are doing client-side only rendering.
+
+jsx +const matches = useMediaQuery('(min-width:600px)', { noSsr: true }); +
+
+or it can turn it on globally with the theme:
+
+```jsx
+const theme = createMuiTheme({

  • props: {
  • MuiUseMediaQuery: {
  • noSsr: true,
  • },
  • },
    +});
    +```
    +
    ## Server-side rendering

鈿狅笍 Server-side rendering and client-side media queries are fundamentally at odds.
@@ -166,10 +189,10 @@ You can reproduce the same behavior with a useWidth hook:
we return a default matches during the first mount. The default value is false.

  • options.matchMedia (Function [optional]) You can provide your own implementation of matchMedia. This can be used for handling an iframe content window.
  • options.noSsr (Boolean [optional]): Defaults to false.

    • In order to perform the server-side rendering reconciliation, it needs to render twice.

    • A first time with nothing and a second time with the children.

    • In order to perform the server-side reconciliation, the hook needs to render twice.

    • A first time with false and a second time with the resolved value.

      This double pass rendering cycle comes with a drawback. It's slower.

    • You can set this flag to true if you are not doing server-side rendering.

    • You can set this option to true if you are doing client-side only rendering.

  • options.ssrMatchMedia (Function [optional]) You can provide your own implementation of matchMedia in a server-side rendering context.

Note: You can change the default options using the default props feature of the theme with the MuiUseMediaQuery key.
@@ -184,7 +207,7 @@ Note: You can change the default options using the [default props](/customizat
import React from 'react';
import useMediaQuery from '@material-ui/core/useMediaQuery';

-export default function SimpleMediaQuery() {
+export default function MyComponent() {
const matches = useMediaQuery('(min-width:600px)');

return {(min-width:600px) matches: ${matches}};
````


However the current behavior is incorrect, and working NOT as documented

I don't understand what you mean by not working at documented?


We might have another improvement opportunity. Looking at this demo, https://codesandbox.io/s/material-demo-w3xyj?file=/demo.tsx we could remove the red flash with:

diff --git a/packages/material-ui/src/useMediaQuery/useMediaQuery.js b/packages/material-ui/src/useMediaQuery/useMediaQuery.js
index d4a7160fd..a04fb4d2e 100644
--- a/packages/material-ui/src/useMediaQuery/useMediaQuery.js
+++ b/packages/material-ui/src/useMediaQuery/useMediaQuery.js
@@ -1,6 +1,11 @@
 import * as React from 'react';
 import { getThemeProps, useTheme } from '@material-ui/styles';

+const useEnhancedEffect =
+  typeof window !== 'undefined' && process.env.NODE_ENV !== 'test'
+    ? React.useLayoutEffect
+    : React.useEffect;
+
 export default function useMediaQuery(queryInput, options = {}) {
   const theme = useTheme();
   const props = getThemeProps({
@@ -54,7 +59,7 @@ export default function useMediaQuery(queryInput, options = {}) {
     return defaultMatches;
   });

-  React.useEffect(() => {
+  useEnhancedEffect(() => {
     let active = true;

     if (!supportMatchMedia) {

Should we move forward with the change? :)

Would this new section in the documentation solve your issue?

And the new option to turn a whole MUI into client-side only mode 馃槈? It should not be would no this case only.
However, even setting noSsr in the theme would resolve my issue today.

what you mean by not working at documented?

The documented behavior is "we need to render twice for proper hydration"
In order to perform the server-side rendering reconciliation, it needs to render twice. A first time with nothing and a second time with the children.

I could read two points from here:

  • it does something only during reconciliation, not every time in every new component
  • A first time with nothing and a second time with the children. is actually partial hydration. And you know - that's a great idea!

useEnhancedEffect

馃憤, that would make the problem a bit less visible.

But I would say

@theKashey I'm sorry, I have some trouble to follow. So you are saying that:

  • We could store, for the whole React tree, a variable that flags when the hydration phase is completed? I'm curious how that would play with partial hydration. Unless React provides a new API, we will not be able to know when the hydration is completed. I would be in favor of waiting for the React team to make progress on this partial hydration feature they talk about in the past, especially if it requires a new API on our side to be implemented. Same problem discussed in https://github.com/streamich/use-media/pull/29.
  • The global switch helps? Great, I'm not aware of any other cases like this one in the codebase.
  • useLayoutEffect is a good direction to explore? Great, however, following this thread https://github.com/streamich/use-media/issues/7 I wonder if allowing useLayout too could make sense. Looking at the list of supported media queries https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries I'm would be favor of useLayoutEffect all-in, and wait for user's feedback.

how that would play with partial hydration

The problem is only with "initial" hydration - incorrect markup could lead to disaster. Later, when "three" is more or less formed it's ok to late-hydrate something not exactly like it was.

Great, I'm not aware of any other cases like this one in the codebase.

Does it matter? Consumer should not think about some implementation details of MUI, like noSSR for useMediaQuery, but answer a simple question - is there any SSR, or not.

I wonder if allowing useLayout too could make sense

I am still about matching queries once and reading them from the context API (like use-media can), that makes testing and SSR easier as well (not like testing use-media ).

What about creating a hybrid solution, where theme matches would be pre-resolved, and all "random" ones would work as right now:

  • theme uses useMediaQuery to match provided breakpoints and conditions, then stores result in the somehere (not sure updating theme object is a good idea)
  • useMediaQuery checks theme(some context) for a predefined value, and uses it in case it exists
  • ssrMatchMedia actually can do it

Hi there! I'm encountering this behavior. I'm not using SSR so I don't really understand how it influences this, but I'm having useMediaQuery return false in the initial call which results in a nasty flash of CSS. A result of true means mobile, so the page is loading initially with the desktop styles (false), then it instantly switches to the mobile styles). Am I doing something wrong, should I be setting some kind of parameter? Thanks!

@DaniGuardiola What do you think of this diff https://github.com/mui-org/material-ui/issues/21142#issuecomment-633144987? The layout effect diff is meant to solve your problem as much as the global option to disable the double rendering. Do you want to work on a pull request? :)

@oliviertassinari ok, I didn't know of the existence of the noSsr option, that fixed it for me. I'm trying to understand the diff but I think I'm missing some basic context about SSR. I understand that by checking the existence of the global window object it is possible to disambiguate whether the code is being run on a client or a server, but I would need to read some more code to fully understand the implications. Would this make it so that the noSsr parameter is not needed for useMediaQuery at all?

And I might be interested in contributing with a PR, but I am not sure I'll have the time for it. Let me learn a bit more about this before I commit to it :)

@DaniGuardiola Ok, no problem, feel free to experiment with these different assumptions when you get the time :)

I would like to work on the issue

@hiteshhv Awesome, it's all yours.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

anthony-dandrea picture anthony-dandrea  路  3Comments

newoga picture newoga  路  3Comments

FranBran picture FranBran  路  3Comments

ryanflorence picture ryanflorence  路  3Comments

sys13 picture sys13  路  3Comments