Using @material-ui/core V4(4.12.3 to be exact) I HAD a custom element created successfully using webpack and babel. I used to be styling it using the @material-ui/core makeStyles. Now I am upgrading to @mui/material v5 and want to use the built-in components from @mui/material but they do not display styled within the custom element. Please note that I need this to be a custom element as it will be integrated within another hosting app.
index.tsx BEFORE in v4
import AppComponent from './App';
import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
import { create } from 'jss';
class MyWebComponent extends HTMLElement {
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: 'open' });
const mountPoint = document.createElement('custom-jss-insertion-point');
const reactRoot = shadowRoot.appendChild(mountPoint);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot,
});
render(
<StylesProvider jss={jss}>
<AppComponent />
</StylesProvider>,
mountPoint
);
}
}
customElements.define('my-element', MyWebComponent);
Upgrading to @mui/material v5(v5.0.4 to be exact), first I tried with StyledEngineProvider in order to mount styles. Then I tried with @mui/styles jssPreset. Either way doesn't work. What I mean by doesn't work is that DataContainer which is referenced by AppComponent has @mui/material components and they are all loading without any styles (such as Grid, Button, InputLabel, Select, any many more).
First try with StyledEngineProvider
import AppComponent from './App';
import { ThemeProvider, createTheme, StyledEngineProvider } from '@mui/material/styles';
import { render } from 'react-dom';
const theme = createTheme();
class MyWebComponent extends HTMLElement {
connectedCallback() {
// can't use jss in mui v5
const shadowRoot = this.attachShadow({ mode: 'open' });
const mountPoint = document.createElement('custom-insertion-point');
const reactRoot = shadowRoot.appendChild(mountPoint);
render(
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<AppComponent />
</ThemeProvider>
</StyledEngineProvider>,
mountPoint //I have also used reactRoot here instead and got same result
);
}
}
customElements.define('my-element', MyWebComponent);
Second try with @mui/styles jssPreset
import AppComponent from './App';
import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '@mui/styles';
import { create } from 'jss';
class MyWebComponent extends HTMLElement {
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: 'open' });
const mountPoint = document.getElementById('jss-insertion-point');
const reactRoot = shadowRoot.appendChild(mountPoint);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot,
});
render(
<StylesProvider jss={jss}>
<AppComponent />
</StylesProvider>,
mountPoint
);
}
}
customElements.define('my-element', MyWebComponent);
AppComponent
import React from 'react';
import { Suspense } from 'react';
import DataContainer from './components/DataContainer';
class AppComponent extends React.Component<any> {
render() {
return (
<Suspense fallback='Loading...'>
<div className='AppComponent'>
<DataContainer />
</div>
</Suspense>
);
}
}
export default AppComponent;
DataContainer
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
const Item = styled(Paper)(({ theme }) => ({
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: 'center',
color: theme.palette.text.secondary,
}));
export default function FullWidthGrid() {
return (
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2}>
<Grid item xs={6} md={8}>
<Button variant="contained">xs=6 md=8</Button>
</Grid>
<Grid item xs={6} md={4}>
<Item>xs=6 md=4</Item>
</Grid>
<Grid item xs={6} md={4}>
<Item>xs=6 md=4</Item>
</Grid>
<Grid item xs={6} md={8}>
<Item>xs=6 md=8</Item>
</Grid>
</Grid>
<div>
<FormControl sx={{ m: 1, minWidth: 180 }}>
<Select autoWidth>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Twenty</MenuItem>
<MenuItem value={21}>Twenty one</MenuItem>
<MenuItem value={22}>Twenty one and a half</MenuItem>
</Select>
</FormControl>
</div>
</Box>
);
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="favicon.ico" />
<link rel="apple-touch-icon" href="logo192.png" />
<link rel="manifest" href="manifest.json" />
<title>React Custom Element</title>
</head>
<body>
<my-element id="elem"> </my-element>
</body>
</html>
This is what I see:
This is what I'm supposed to see as shown in this stackblitz. (Note that I was unable to create a stackblitz with a custom element unfortunately)
CodePudding user response:
Here is how I would do it:
You need to create style
tag. This will be entry point to emotion (material ui 5 styling solution) to insert scoped shadow DOM styles.
Next step is to configure jss and emotion cache
const jss = create({
...jssPreset(),
insertionPoint: reactRoot,
});
const cache = createCache({
key: 'css',
prepend: true,
container: emotionRoot,
});
Last thing to do is to wrap our tree in providers
render(
<StylesProvider jss={jss}>
<CacheProvider value={cache}>
<ThemeProvider theme={theme}>
<Demo />
</ThemeProvider>
</CacheProvider>
</StylesProvider>,
mountPoint
);
Full example:
import React from 'react';
import Demo from './demo';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { StylesProvider, jssPreset } from '@mui/styles';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
import { create } from 'jss';
import { render } from 'react-dom';
const theme = createTheme();
class MyWebComponent extends HTMLElement {
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: 'open' });
const emotionRoot = document.createElement('style');
const mountPoint = document.createElement('div');
shadowRoot.appendChild(emotionRoot);
const reactRoot = shadowRoot.appendChild(mountPoint);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot,
});
const cache = createCache({
key: 'css',
prepend: true,
container: emotionRoot,
});
render(
<StylesProvider jss={jss}>
<CacheProvider value={cache}>
<ThemeProvider theme={theme}>
<Demo />
</ThemeProvider>
</CacheProvider>
</StylesProvider>,
mountPoint
);
}
}
if (!customElements.get('my-element')) {
customElements.define('my-element', MyWebComponent);
}