Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

material-ui / jss / dynamic imports #99

Closed
aheissenberger opened this issue Jan 5, 2018 · 12 comments
Closed

material-ui / jss / dynamic imports #99

aheissenberger opened this issue Jan 5, 2018 · 12 comments

Comments

@aheissenberger
Copy link

The default class names (.jss123) created by jss will be merged from different dynamic imports which breaks the design.

Setup:

Solution:
replace default name generator (http://cssinjs.org/js-api/?v=v9.5.0#generate-your-own-class-names) with a variation which uses random strings as part of the class name and wrap your app with this HOC.

import React, { Component } from 'react';
import JssProvider from 'react-jss/lib/JssProvider';
import { create } from 'jss';
import preset from 'jss-preset-default';
//import createGenerateClassName from 'material-ui/styles/createGenerateClassName';

import Layout from './components/pages/layout';

const createGenerateClassName = () => {
  let counter = 0
  return (rule, sheet) => `c${Math.random().toString(36).substring(2, 4) + Math.random().toString(36).substring(2, 4)}-${rule.key}-${counter++}`
}

const jss = create(preset());
// Custom Material-UI class name generator for better debug and performance.
jss.options.createGenerateClassName = createGenerateClassName;

function App() {
  return (
    <JssProvider jss={jss}>
      <Layout />
    </JssProvider>
  );
}

export default App;

please add this to the recipe section of your docs

@stereobooster
Copy link
Owner

stereobooster commented Jan 5, 2018

I do not fully understand problem. I will need to investigate. Thanks for the tip

@stereobooster
Copy link
Owner

stereobooster commented Jan 5, 2018

Yes I was able to reproduce. Strange thing. I wonder how JSS works with SSR.

UPD: oh my

Once JS on the client is loaded, components initialized and your JSS styles are regenerated, it's a good time to remove server-side generated style tag in order to avoid side-effects

https://github.com/cssinjs/jss/blob/master/docs/ssr.md

render(<Button />, document.getElementById('app'), () => {
  // We don't need the static css any more once we have launched our application.
  const ssStyles = document.getElementById('server-side-styles')
  ssStyles.parentNode.removeChild(ssStyles)
})

Another fix

basically JSS doesn't support rehydration

import React from 'react';
import { hydrate, render } from 'react-dom';
import { loadComponents } from 'loadable-components';
import { getState } from 'loadable-components/snap';
import Index from './pages/index';

const app = <Index />;
const rootElement = document.getElementById('root');

loadComponents().then(() => {
  render(app, rootElement, () => {
    Array.from(document.querySelectorAll('[data-jss-snap]')).forEach(elem =>
      elem.parentNode.removeChild(elem),
    );
  });
});

window.snapSaveState = () => {
  Array.from(document.querySelectorAll('[data-jss]')).forEach(elem =>
    elem.setAttribute('data-jss-snap', ''),
  );
  return getState();
};

@stereobooster
Copy link
Owner

Thank you @aheissenberger

@nimahkh
Copy link

nimahkh commented Oct 6, 2018

function App() {

perfect, this solution was amazing , thanks a lot

@alan345
Copy link

alan345 commented Oct 17, 2018

thanks @aheissenberger and @stereobooster! Very helpful!

@chomamateusz
Copy link

@aheissenberger I had today same problem with React.lazy() and JSS from Material-UI, without SSR. Your solution seems to be helpful. Do you think it is 1 to 1 related? I think so, but I'm not 100% sure.

rockiger added a commit to rockiger/junto that referenced this issue Feb 3, 2020
…box is big and blue#101. Create own name generator for css-classes to dodge conflicts in prerenderes versions. Otherwise we have totally unexspected behavior with our styling. stereobooster/react-snap#99
rockiger added a commit to rockiger/junto that referenced this issue Nov 13, 2020
…box is big and blue#101. Create own name generator for css-classes to dodge conflicts in prerenderes versions. Otherwise we have totally unexspected behavior with our styling. stereobooster/react-snap#99
@bartosz-lobejko-trabr
Copy link

bartosz-lobejko-trabr commented Dec 8, 2020

Another solution

const generateClassName = createGenerateClassName({
  productionPrefix: navigator.userAgent === 'ReactSnap' ? 'snap' : 'jss',
});

<StylesProvider jss={jss} generateClassName={generateClassName}>
    <Layout/>
</StylesProvider>

@mwskwong
Copy link

mwskwong commented Mar 3, 2021

A much easier solution for material-ui users.

For people who are using material-ui v4, wrap the components in <NoSsr/>.

For material-ui v5, which is currently in the alpha stage, this will no longer be an issue in the future since it is migrating to emotion.

@oseisaac
Copy link

oseisaac commented May 5, 2021

<NoSsr/>.

@matthewkwong2
how do ou know what components to wrap with <NoSsr/> with a large application

@mwskwong
Copy link

mwskwong commented May 5, 2021

<NoSsr/>.

@matthewkwong2
how do ou know what components to wrap with <NoSsr/> with a large application

If you want to do pre-rendering on a large scale project, then react-snap is not the right tool. It is more or less an experiment and prove of concept and have a lot of imperfection. Instead, you should use a proper solution like Gatsby.

@oseisaac
Copy link

oseisaac commented May 6, 2021 via email

@iuliancmarcu
Copy link

iuliancmarcu commented Mar 31, 2022

In our case of using react-snap, with react-jss, we realised that for some reason, the styles created while running react-snap (so Puppeteer) are missing some selectors and styles.

Here is the solution we ended up with, that works every time:

import React from 'react';
import { render, hydrate } from 'react-dom';
import { createGenerateId, JssProvider, SheetsRegistry } from 'react-jss';

import App from './App';

const rootElement = document.getElementById('root');
if (rootElement && rootElement.hasChildNodes()) {
  hydrate(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
    rootElement,
    () => {
      const reactSnapStyles = document.getElementById('react-snap-styles');
      reactSnapStyles?.parentNode?.removeChild(reactSnapStyles);
    },
  );
} else {
  const registry = new SheetsRegistry();
  const generateId = createGenerateId();

  render(
    <JssProvider registry={registry} generateId={generateId}>
      <React.StrictMode>
        <App />
      </React.StrictMode>
    </JssProvider>,
    rootElement,
    () => {
      if (navigator.userAgent === 'ReactSnap') {
        const badStyles = document.querySelectorAll('[data-jss]');
        badStyles.forEach((cssStyle) => cssStyle.parentNode?.removeChild(cssStyle));

        const style = document.createElement('style');
        style.innerHTML = registry.toString();
        style.setAttribute('id', 'react-snap-styles');

        const head = document.querySelector('head');
        head.appendChild(style);
      }
    },
  );
}

Basically, when we're running the app with react-snap, we remove the generated react-jss styles (since they might be broken) and we append a <style id="react-snap-styles"> to head, with all the styles we collected in the SheetsRegistry. Then, when we get to rehydrate the app, after everything is rendered (and snap generated new styles), we remove the #react-snap-styles element.

I hope this helps people that might end up here with a similar issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants