Change props depending on breakpoint with SSR support

Viewed 580

I'm using material-ui@v5, ie. the alpha branch.

Currently, I have a custom Timeline component which does this:

const CustomTimeline = () => {
  const mdDown = useMediaQuery(theme => theme.breakpoints.down("md"));

  return (
    <Timeline position={mdDown ? "right" : "alternate"}>
      {/* some children */}
    </Timeline>
  );
};

It works mostly as intended, but mobile users may experience layout shift because useMediaQuery is implemented using JS and is client-side only. I would like to seek a CSS implementation equivalence to the above code to work with SSR.

I have thought of the following:

const CustomTimeline = () => {

  return (
    <Fragment>
      <Timeline sx={{ display: { xs: "block", md: "none" } }} position="right">
        {/* some children */}
      </Timeline>
      <Timeline sx={{ display: { xs: "none", md: "block" } }} position="alternate">
        {/* some children */}
      </Timeline>
    </Fragment>
  );
};

This will work since the sx prop is converted into emotion styling and embedded in the HTML file, but this will increase the DOM size. Is there a better way to achieve that?

2 Answers

I have experienced the same problem before and I was using Next.js to handle SSR. But it does not matter.

Please first install this package and import it on your root, like App.js

import mediaQuery from 'css-mediaquery';

Then, create this function to pass ThemeProvider of material-ui

  const ssrMatchMedia = useCallback(
        (query) => {
            const deviceType = parser(userAgent).device.type || 'desktop';
            return {
                matches: mediaQuery.match(query, {
                    width: deviceType === 'mobile' ? '0px' : '1024px'
                })
            };
        },
        [userAgent]
    );

You should pass the userAgent!

Then pass ssrMatchMedia to MuiUseMediaQuery

<ThemeProvider
                theme={{
                    ...theme,
                    props: {
                        ...theme.props,
                        MuiUseMediaQuery: {
                            ssrMatchMedia
                        }
                    }
                }}>

This should work. I am not using material-UI v5. Using the old one. MuiUseMediaQuery name might be changed but this approach avoid shifting for me. Let me know if it works.

To avoid first render before useMediaQuery launches From reactjs docs To fix this, either move that logic to useEffect (if it isn’t necessary for the first render), or delay showing that component until after the client renders (if the HTML looks broken until useLayoutEffect runs).

To exclude a component that needs layout effects from the server-rendered HTML, render it conditionally with showChild && and defer showing it with useEffect(() => { setShowChild(true); }, []). This way, the UI doesn’t appear broken before hydration.

Related