hupe1980 / gatsby-plugin-material-ui

Gatsby plugin for Material-UI with built-in server-side rendering support
MIT License
136 stars 25 forks source link

Incorrect styling after switching to client side #61

Open morawskiOZ opened 3 years ago

morawskiOZ commented 3 years ago

Problem: After switching from SSR to client-side and dom hydration some of the jss styles are off and the whole layout is out of whack. Screenshots with an affected component for both server and client renders: server client

What you see on the screenshots is class name collision, somehow after hydration class name was generated again with a lower index which was already taken. I tried adding seed to the classnames generator, the collision was gone but the class was simply empty after hydration.

What I've already found:

I traced the problem to this gatsby issue https://github.com/gatsbyjs/gatsby/issues/17676. The solution with replacing hydration function from hydrate to render. It works, but it is a performance hit and with React 17 it is said it soon will be deprecated Solution:

//gatsby-broswer.js
export function replaceHydrateFunction() {
  return (element, container, callback) => {
    ReactDOM.render(element, container, callback)
  }
}

Fixable ? Any tips for a better solution? I am really surprised nobody else has had this problem. Any chance we might fix this within the plugin?

mpolinowski commented 3 years ago

Hey @morawskiOZ,

I am running into the same issue - but replaceHydrateFunction doesn't solve it for me. When I first land on my page the CSS is broken as soon as it rehydrates:

Untitled

I have to click on an internal link to leave the page and return and the correct CSS shows up. I am using the material UI starter and I just started to rewrite all my components with styled-components. So the s-c library is present and configured. But I still have some JSS present. I am not sure if this is my issue.

Are you using styled-components? And did you make any other changes to the official starter? And are you on Material-UI 4 or already on the 5 preview?

siriwatknp commented 3 years ago

I have this issue on my open source website also. Still dont know how to fix it. https://mui-treasury.com/components/card

oliviertassinari commented 3 years ago

The best approach here is to work with a dichotomy, remove as much code as possible to keep reproducing the issue.

mpolinowski commented 3 years ago

@siriwatknp,

I found my problem - I believe the issue lies in { withStyles } from '@material-ui/core/styles'. My gatsby page uses the Layout plugin - which means that every page component becomes a child of the layout component.

I am using withStyles in my layout component and also in some of pages - which does not work. I am currently switching everything to styled-components and inline styles for mui-components and this solves the issue for me.

morawskiOZ commented 3 years ago

Hey @morawskiOZ,

I am running into the same issue - but replaceHydrateFunction doesn't solve it for me. When I first land on my page the CSS is broken as soon as it rehydrates:

Untitled

I have to click on an internal link to leave the page and return and the correct CSS shows up. I am using the material UI starter and I just started to rewrite all my components with styled-components. So the s-c library is present and configured. But I still have some JSS present. I am not sure if this is my issue.

Are you using styled-components? And did you make any other changes to the official starter? And are you on Material-UI 4 or already on the 5 preview?

Hey @mpolinowski Sorry for the late response. I see you fixed your problem somewhat but I am still gonna respond. I am running pure MUI v5 without Styled Components. The initial setup was pretty minimal almost official starter level. I tested a similar setup with Next and custom CRA and didn't have any problems so it must be Gatsby's hydration issue.

mpolinowski commented 3 years ago

Hey @mpolinowski Sorry for the late response. I see you fixed your problem somewhat but I am still gonna respond. I am running pure MUI v5 without Styled Components. The initial setup was pretty minimal almost official starter level. I tested a similar setup with Next and custom CRA and didn't have any problems so it must be Gatsby's hydration issue.

Ok thanks!

Yes, it turned out that my issue had nothing to do with mui v4 vs v5 or styled-components. All of this works... if you use it the way it is intended to be used...

Next.js is a good tip - I will give it a try in my next project.

visualjeff commented 3 years ago

Run into this same issue with a POC our company is testing. Is this really a Gatsby hydration issue? It sounds from previous comments like it has nothing to do with mui or styled-components. Is this an accurate statement?

mpolinowski commented 3 years ago

It sounds from previous comments like it has nothing to do with mui or styled-components. Is this an accurate statement?

For me, using the layout plugin in combination with the HOC withStyles was the issue. Everything works as expected now after removing the HOC's from my child components.

Are you using deeply nested components? I had the same issue in the past with a page that was nested 4 layers deep - layout / page / child / child-of-the-child. The component was breaking on first pageload and when you reloaded the page. I re-wrote it so that I could remove the lowest layer and the issue went away.

I am not sure if that actually was the solution. But I have been taking care that I do not go deeper than the 3rd layer below the layout and the issue never returned. It might have also been related to the HOC issue above, though.

dungle-scrubs commented 3 years ago

I've also just started experiencing this issue. I'll keep my progress updated while following the advice in this thread and fixing it, but though I'd post just in case.

We've been building a site for a few months now and it's only happening on the Footer component as of a few days ago. Often, the first load is fine, then all subsequent loads I can see the styles correct for a moment, then they change. We're not using any styled components and 100% of our styles are done through Mui's makeStyles.

The classes created by makeStyles are all there (although named differently for production), but I can tell which ones they are. Their css properties just completely change after page load (not just disappear. Some css properties remain, some vanish, and some new ones are created).

Footer is part of Layout and layout wraps all pages via wrapPageElement in both gatsby-ssr.js and gatsby-browser.js.

dungle-scrubs commented 3 years ago

It turns out that for me the problem started when I enabled gatsby-plugin-offline and ended when I remove it.

dungle-scrubs commented 3 years ago

Hey @mpolinowski,

Yes, it turned out that my issue had nothing to do with mui v4 vs v5 or styled-components. All of this works... if you use it the way it is intended to be used...

Thx for yours and everyone's input. Would you mind expanding on this statement?

Although removing gatsby-plugin-offline solved the styling issue for me on page load, it's still happening for me, only on one component, under specific conditions.

// This is a Gatsby page built from a template

import React from 'react'
import { Router } from '@reach/router'

// Components
import SEO from '../../components/seo'
import AuthWrapper from '../../components/auth/authWrapper'
import Login from '../../components/auth/login'
import Register from '../../components/auth/register'
import AccountPortal from '../../components/auth/accountPortal'
import Addresses from '../../components/auth/addresses'
import ForgotPassword from '../../components/auth/forgotPassword'
import ResetPassword from '../../components/auth/resetPassword'
import Activate from '../../components/auth/activate'

// Strings
import strings from './strings'

const AccountPage = (props) => {
  return (
    <>
      <SEO title={strings.seoTitle} shopifyThemePath={shopifyThemePath} />
      <Router basepath="/account">
        <AuthWrapper path="/" component={AccountPortal} currency={currency} />
        <AuthWrapper path="/addresses" component={Addresses} />
        <ResetPassword path="/reset/:id/:token" />
        <Login path="/login" />
        <Activate path="/activate/:id/:token" />
        <Register path="/register" />
        <ForgotPassword path="/forgot_password" />
      </Router>
    </>
  )
}

export default AccountPage

I have it set up so that if you link internally to any page starting with /account/* you are redirected to the router's basepath of /account. AuthWrapper will learn if the user is logged in or not and render the account portal or redirect them to Login if not. No issues at this point.

However, if linked directly to a nested account page, such as /account/login or account/register, the Footer styles are correct for a moment, then they completely break.

It's worth noting that in this situation, landing on /account/login, will immediately redirect to /account, then quickly back to /account/login after AuthWrapper does its job. It breaks somewhere during that process.

What I'm confused about is that in all cases, it's only the Footer component which is called in the Layout component that breaks. Nothing else in the site is affected, including the Header, which is also in Layout.

// Layout.js

// ..imports

const Layout = (props) => {
  return (
    <>
      <CssBaseline />
      <SEO />
      <Header {...props} />
      <main>{props.children}</main>
      <Footer />
    </>
  )
}

export default Layout

The footer:

// Footer.js

import React from 'react'
import { useStaticQuery, graphql } from 'gatsby'
import { makeStyles } from '@material-ui/core'
import Box from '@material-ui/core/Box'
import Container from '@material-ui/core/Container'
import SvgIcon from '@material-ui/core/SvgIcon'

// Components
import NavList from '../navList'
import Social from '../social'
import AcceptedPayments from '../acceptedPayments'

// Assets
import Logo from './assets/logo.inline.svg'

// Styles
const useStyles = makeStyles((theme) => ({
  root: {
    paddingTop: theme.spacing(3),
    paddingBottom: theme.spacing(3),
    backgroundColor: theme.palette.secondary.main,
    color: theme.palette.common.white,
    marginTop: theme.spacing(10),
    [theme.breakpoints.up('md')]: {
      marginTop: theme.spacing(15),
      paddingTop: theme.spacing(6),
      paddingBottom: theme.spacing(6),
    },
  },
  container: {
    display: 'grid',
    gap: theme.spacing(3),
    [theme.breakpoints.up('md')]: {
      gridTemplateColumns: '1fr auto',
      alignItems: 'start',
    },
  },
  logo: {
    height: '80px',
    width: '208px',
  },
  innerWrapper: {
    display: 'grid',
    gap: theme.spacing(3),
    [theme.breakpoints.up('sm')]: {
      gridTemplateColumns: 'auto 1fr',
      alignItems: 'center',
      order: 1,
    },
    [theme.breakpoints.up('md')]: {
      gridTemplateColumns: 'auto',
    },
  },
  navWrapper: {
    display: 'grid',
    gap: theme.spacing(3),
    [theme.breakpoints.up('sm')]: {
      gridTemplateColumns: '1fr 1fr 1fr',
    },
  },
  navList: {
    [theme.breakpoints.only('xs')]: {
      borderBottom: '1px solid hsla(199, 59%, 33%, 1)',
    },
  },
}))

const Footer = (props) => {
  const data = useStaticQuery(graphql`
    {
      prismicNavigation {
        data {
          nav_footer {
            ... on PrismicNavigationNavFooterNavList {
              primary {
                type
                label
                handle
                link {
                  type
                  uid
                  url
                }
              }
              items {
                type
                label
                handle
                link {
                  type
                  uid
                  url
                }
              }
            }
          }
        }
      }
    }
  `)

  const navMenus = data.prismicNavigation.data.nav_footer

  const classes = useStyles()

  // console.log('👾 Footer', navMenus)
  return (
    <>
      <Box className={classes.root} component="footer">
        <Container className={classes.container}>
          <Box className={classes.innerWrapper}>
            <SvgIcon className={classes.logo} component={Logo} viewBox="0 0 208 80" />
            <Social />
          </Box>
          <Box className={classes.navWrapper}>
            {navMenus.map((menu, i) => (
              <NavList key={i} styles={classes.navList} {...menu} />
            ))}
          </Box>
        </Container>
      </Box>
      <AcceptedPayments styles={classes.acceptedPayments} />
    </>
  )
}

export default Footer

There is something special about the Footer that Gatsby/Mui doesn't like and I haven't been able to figure out what it is.

Thoughts?