Duplicate of #21048 - [useMediaQuery] always returns false first at page-load
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 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.
@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 useMediaQueryglobal variableFrom 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({
鈿狅笍 Server-side rendering and client-side media queries are fundamentally at odds.
@@ -166,10 +189,10 @@ You can reproduce the same behavior with auseWidthhook:
we return a default matches during the first mount. The default value isfalse.
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 tofalse.
- 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
falseand 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
trueif you are not doing server-side rendering.
- You can set this option to
trueif 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:
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:
useLayoutEffect all-in, and wait for user's feedback. 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.
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 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 existsssrMatchMedia actually can do itHi 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.
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
useMediaQueryreturn false in the initial call which results in a nasty flash of CSS. A result oftruemeans 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!