gatsby: Material-UI and Gatsby v2 - hot reload does not work for styles

This is basically a re-post from the Material-UI issue in hopes that someone can help me.

I’m using Material-UI v3.1.0 with Gatsby v2.0.0 following the official example. Gatsby v2 changed how layout works, specifically the changes after gatsby@2.0.0-beta.59 caused many headaches for me. I managed to get my production build to work perfectly, but development is a different story. Styles work, but hot reload does not, which makes dev story pretty bad. Having to refresh the page after each style change sucks big time.

I don’t have time to debug it ATM, going for a short break, but decided to post the issue. Maybe it magically resolves when I come back a week later 😃

My use case is a bit different than the example. I use Gatsby’s new wrapPageElement to wrap page with my custom Layout. Inside that layout, I use withRoot from the example. This way, JssProvider and MuiThemeProvider are created only once, not each time a different page is rendered. I suspect this might be the reason why hot reload does not work. Then again, it could be related to babel@7 problems with RHL. Note that in the example, hot reload works, but it’s not using the wrapPageElement api. Would be cool if there was some example using it.

Furthermore, JssProvider is also created inside replaceRenderer method. So now I have two JSS providers on SSR: one in replaceRenderer and one in withRoot. This is bad, right? I already created an issue on Gatsby’s issue tracker, but got nowhere. I guess there’s not so many people living on the edge 😃

Anyway, here’s my code:

(note that gatsby-browser.js is pretty much the same as gatsby-ssr.js without the replaceRenderer)

const React = require('react')
const { renderToString } = require('react-dom/server')
const { JssProvider } = require('react-jss')

const postcss = require('postcss')
const autoprefixer = require('autoprefixer')
const csswring = require('csswring')

const { AdyenProvider } = require('providers/adyen')
const { CartProvider } = require('providers/cart')
const getPageContext = require('utils/getPageContext').default
const Layout = require('components/layout/Layout').default

const prefixer = postcss([autoprefixer])
const minifier = postcss([csswring])

exports.wrapRootElement = ({ element }) => {
  return (
    <AdyenProvider>
      <CartProvider>
        {element}
      </CartProvider>
    </AdyenProvider>
  )
}

exports.wrapPageElement = ({ element, props }) => {
  return (
    <Layout {...props}>{element}</Layout>
  )
}

exports.replaceRenderer = ({ bodyComponent, replaceBodyHTMLString, setHeadComponents }) => {
  const muiPageContext = getPageContext()

  const bodyHTML = renderToString(
    <JssProvider registry={muiPageContext.sheetsRegistry}>
      {bodyComponent}
    </JssProvider>
  )

  replaceBodyHTMLString(bodyHTML)

  // extract and minify styles
  const css = muiPageContext.sheetsRegistry.toString()
  const options = { from: undefined }
  const prefixed = prefixer.process(css, options)
  const minified = minifier.process(prefixed.css, options)

  setHeadComponents([
    <style
      type={'text/css'}
      id={'server-side-jss'}
      key={'server-side-jss'}
      dangerouslySetInnerHTML={{ __html: minified.css }}
    />,
  ])
}

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 8
  • Comments: 48 (34 by maintainers)

Commits related to this issue

Most upvoted comments

This seriously should be solved.

I have started working on the problem. You can expect a first-class support of Material-UI v4 with Gatsby. We gonna use https://github.com/hupe1980/gatsby-plugin-material-ui for that.

I fiddled around, thats what I found:

  • wrapRootElement and the MuiThemeProvider seem to be unrelated to hot module reloading not working. Plain withStyles() without theme do not trigger a HMR as well.
  • DOM wise:
  • I tried gatsby-plugin-jss and it’s example in the gatsby repo. Here HMR is broken as well, but the behavior is mirrored: New class names are set for the components, the new styles are not injected in the DOM.
  • It seems like, after the HMR, the old sheetManager is still used to get the class names for the component. A new sheetManager exists in sheetsManager with the new styles applied, but that one is not used. Thats why nothing changes, the css is there but the class names are not updated. Probably this line: https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/styles/withStyles.js#L195
  • Thewhich results in stylesCreatorSaved staying the same value all the time
  • componentDidUpdate is not called at all, the HoC is actually reconstructed and remounted every time the styles get updated. I guess here lies the problem.
  • Maybe this is related or could help: https://github.com/mui-org/material-ui/issues/13465

I hope this helps @HriBB 😃

I think that we can close the issue. react-hot-loader is supported with the higher-order and hook APIs:

feb-20-2019 12-19-39

This is using Material-UI v4.0.0-alpha.1 and this example: https://github.com/mui-org/material-ui/tree/next/examples/gatsby-next.

Is SSR with Gatsby and MUI currently broken? I am not able to get things working and the message in the example says it’s not ready yet

Is there any workaround?

@oliviertassinari Any ideas?

FWIW, I don’t remember this being an issue when I filed #7716 and #7568 (there was another issue, related to not being able to track styles when building more than one page).

I’m sure @oliviertassinari’s method works, but you then lose out on the ability to have smooth transitions between pages with shared layouts…

Which is what wrapPageElement and wrapRootElement were intended to resolve.

Looking through the current open issues, it seems that HMR is responsible for quite a few. Maybe it’s worth looking at how Next.js migrated away from react-hot-loader, since it seems like it has caused on-going issues.

I noticed in the changelog that it used to have issues with stateless components, so I tried converting all my stateless components to React.Component but that didn’t resolve this issue.

My biggest complaint with Gatsby is that I can have something looking amazing in the develop mode, but it always has countless issues to debug when I try to build.

This issue is still there and can be reproduced by the latest material example of gatsby. I can provide a codesandbox url if you need, since it requires some work.

It’s fixed in https://github.com/mui-org/material-ui/pull/16195 and will be soon released in Material-UI v4.1.1.

Just tested a production build and it worked successfully! no more first paint issues

Note per https://www.gatsbyjs.org/packages/gatsby-plugin-offline/#remove:

 plugins: [
-  `gatsby-plugin-offline`,
+  `gatsby-plugin-remove-serviceworker`,
 ]

It’s now suggested to add the gatsby-plugin-remove-serviceworker plugin to ensure that the service worker is removed completely. (Otherwise the service worker still tries to take over and jumbles all the styles.) I previously had to force-clear the site’s app data in Chrome, which I obviously couldn’t expect users of a public site to do.

I managed to get hot reloading to work by using export default hot(module)(PageOrTemplate).

Had to add this to all my pages and templates. Hacky, but at least I can work normally …

// @flow

import React from 'react'
import { hot } from 'react-hot-loader'
import { Trans } from '@lingui/react'

import Content from 'components/ui/Content'
import Section from 'components/ui/Section'
import Title from 'components/ui/Title'

import HeroImage from 'components/about/HeroImage'

const HomePage = () => {
  return (
    <Content>
      <HeroImage />
      <Section first>
        <Title>
          <Trans>Hot reloading</Trans>
        </Title>
      </Section>
    </Content>
  )
}

export default hot(module)(HomePage)

Temporal workaround from react-hot-loader issue:

If you use the gatsby example from the MUI project: You need to paste

import { setConfig } from 'react-hot-loader'

setConfig({
  reloadHooks: false
})

on top of the function definition of your root layout i.e. the top layout of the plugin.

It’s a recent regression with an update of react-hot-loader.

this is linked from https://github.com/mui-org/material-ui/blob/master/examples/gatsby/gatsby-ssr.js so I thought I’d jump back in, though I originally commented on hot reloading, i’ve progressed on the project and got to production issues. Here’s what worked for me.

export const replaceHydrateFunction = () => {
  return (element, container, callback) => {
    ReactDOM.render(element, container, callback)
  }
}

The last bullet is the new suggestion on the thread. Without it, the initial page would look as expected from html and then when the js loaded the styles got weird.