Nested ThemeProvider overrides theme style definition

607 views Asked by At

I need to be able to render a component and then do additional rendering using useEffects. In the code below the component is provided with a theme and each useEffect renders components that have their own theme.

But in the final react rendering the original component has its style overridden by the theme of the component of the second useEffect.

Using this setup how can I have each theme be applied to its corresponding components without leaking styles?

Using React 17.0.2 and material-ui 4.12.4 here is the relevant code:

import React from "react";
import ReactDOM from "react-dom";

import { createTheme, ThemeProvider } from "@material-ui/core/styles";
import CssBaseline from "@material-ui/core/CssBaseline";

import orange from "@material-ui/core/colors/orange";
import green from "@material-ui/core/colors/green";
import Checkbox from "@material-ui/core/Checkbox";
import Person from "@material-ui/icons/Person";

const greenTheme = createTheme({
  palette: {
    secondary: {
      main: green[500]
    }
  }
});

const orangeTheme = createTheme({
  palette: {
    secondary: {
      main: orange[500]
    }
  }
});

function App() {
  React.useEffect(() => {
    const div = document.createElement("div");
    div.style.backgroundColor = "#ccc";

    ReactDOM.render(
      <ThemeProvider theme={greenTheme}>
        <CssBaseline />
        <div>
          <Checkbox defaultChecked />
          <Person color="secondary" />
        </div>
      </ThemeProvider>
      , div
    );

    document.getElementById("root").append(div);
  });

  React.useEffect(() => {
    const div = document.createElement("div");
    div.style.backgroundColor = "#ccc";

    ReactDOM.render(
      <ThemeProvider theme={orangeTheme}>
        <CssBaseline />
        <div>
          <Checkbox defaultChecked />
          <Person color="secondary" />
        </div>
      </ThemeProvider>
      , div
    );

    document.getElementById("root").append(div);
  });

  return (
    <ThemeProvider theme={greenTheme}>
      <CssBaseline />
      <Checkbox defaultChecked />
      <Person color="secondary" />
      <div>
        The color of above icon should be green but is getting overriden
      </div>
    </ThemeProvider>
  );
}

export default App;

Edit Material nested themes demo (forked)

2

There are 2 answers

2
Nic Estrada On

The way you are rendering inside effects is very strange. I can't think of a reason you would have to do that. Would you be able to do something like this instead?

import React from "react";
import ReactDOM from "react-dom";

import { createTheme, ThemeProvider } from "@material-ui/core/styles";
import CssBaseline from "@material-ui/core/CssBaseline";

import orange from "@material-ui/core/colors/orange";
import green from "@material-ui/core/colors/green";
import Checkbox from "@material-ui/core/Checkbox";
import Person from "@material-ui/icons/Person";

const greenTheme = createTheme({
  palette: {
    secondary: {
      main: green[500]
    }
  }
});

const orangeTheme = createTheme({
  palette: {
    secondary: {
      main: orange[500]
    }
  }
});

function Div1() {
  return (
    <div style={{backgroundColor: "#ccc"}}>
      <ThemeProvider theme={greenTheme}>
        <CssBaseline />
        <div>
          <Checkbox defaultChecked />
          <Person color="secondary" />
        </div>
      </ThemeProvider>
    </div>
  );
}

function Div2() {
  return (
    <div style={{backgroundColor: "#ccc"}}>
      <ThemeProvider theme={orangeTheme}>
        <CssBaseline />
        <div>
          <Checkbox defaultChecked />
          <Person color="secondary" />
        </div>
      </ThemeProvider>
    </div>
  );
}

function App() {
  return (
    <>
      <ThemeProvider theme={greenTheme}>
        <CssBaseline />
        <Checkbox defaultChecked />
        <Person color="secondary" />
        <div>
          The color of above icon should be green but is getting overriden
        </div>
      </ThemeProvider>
      <Div1 />
      <Div2 />
    </>
  );
}

export default App;

This way, each div is now its own component and you just render them like normal. If you want them as sibling elements to the ThemeProvider in the App component, then you can do that in the jsx. Just use fragments (<></>) to make sure the function only returns one element.

0
gaitat On

To answer my own question, following the comments in https://github.com/mui/material-ui/issues/15914 the <ThemeProvider> should be surrounded by a <StyleProvider>.

As a result the relevant code would become:

      <StylesProvider generateClassName={generateClassName("a")}>
        <ThemeProvider theme={orangeTheme}>
          <CssBaseline />
          <div>
            <Checkbox defaultChecked />
            <Person color="secondary" />
          </div>
        </ThemeProvider>
      </StylesProvider>,

Now the correct colors appear with no style leaking.