Missing styles when using React with Web Components - reactjs

I'm creating a widget module that will then be assembled into a bundle and connected to the static sites in a single file. One of the main problems is applying the styles of the parent sites to the widget module. Because of this, an attempt was made to encapsulate the widget using the shadow dom.
import React from 'react';
import ReactDOM from 'react-dom';
import styled from 'styled-components';
const StyledButton = styled.button`...`;
function ModuleComponent() {
return (
<div>
<StyledButton>I does not work</StyledButton>
<button style={{ height: 40, color: "red" }}>It works</button>
</div>
);
}
class IncapsulatedReactWidgetElement extends HTMLElement {
connectedCallback() {
const mountPoint = document.createElement("div");
this.attachShadow({ mode: "open" }).appendChild(mountPoint);
ReactDOM.render(<ModuleComponent />, mountPoint);
}
}
customElements.define(
"incapsulated-react-widget-element",
IncapsulatedReactWidgetElement
);
function Root() {
return <incapsulated-react-widget-element />;
}
const MOUNT_NODE = document.getElementById("root");
ReactDOM.render(<Root />, MOUNT_NODE);
Actually it works as expected except the styles. I'm trying to use styled components but nothing works expect inline styles. As you can see, I do two render actions: first for integration ModuleComponent into custom element and second - for render the app. In which way styles can get lost?

Correct way for applying styles is using Style Hook for React. Make use of makeStyles and useStyles and its an standard way of doing it.
This may help for quick start: https://material-ui.com/styles/basics/#hook-api
The inline styles that you have used are also kinda a subset of it.
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
const useStyles = makeStyles({
root: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
},
});
export default function Hook() {
const classes = useStyles();
return <Button className={classes.root}>Hook</Button>;
}
if you are going to have larger stylesheets, then we can separate it to different file. For site wide changes, you can use createStyle hook.

Related

Better way to use material-system with styled-components

I am trying to use the stack above to customize Material components with the props provided by the Material system (joining with styled-components).
The component that I'm testing:
import React from 'react'
import Grid from '#material-ui/core/Grid'
import { spacing, sizing, color, flexbox, display } from '#material-ui/system'
import styled from 'styled-components'
const Row = styled(props => <Grid container {...props} />)`
${spacing}
`
export default Row
Everything on screen works very well, but an error on console appear every time that I use a material-system prop. Ex.:
<Row maxWidth='200px'>...</Row>
the following error appear on console:
Warning: React does not recognize the maxWidth prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase maxwidth instead. If you accidentally passed it from a parent component, remove it from the DOM element.
I know that the ...props is passing maxWidth to Grid component (and it's a html element), but I'm confusing about how to use it without these console log's
Let's separate exactly which props are used for styling and which props are sent to the Grid component. Using rest, we can "pull out" certain properties from the props object and send the rest to the Grid component. Then, the properties that we pull out will form the CSS.
import React from 'react'
import Grid from '#material-ui/core/Grid'
import { spacing, sizing, color, flexbox, display } from '#material-ui/system'
import styled from 'styled-components'
const Row = styled(
({ maxWidth, somethingElse, ...rest }) => <Grid container {...rest} />
)`
${spacing}
maxWidth: ${props => props.maxWidth || defaultValue1};
somethingElse: ${props => props.somethingElse || defaultValue2};
`;
export default Row
The Material-UI documentation shows examples of various approaches to styling, including an example showing how to use styled-components with Material-UI.
Here is the example they show:
import React from 'react';
import { styled } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
const MyButton = styled(Button)({
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
});
export default function StyledComponents() {
return <MyButton>Styled Components</MyButton>;
}
Another approach is using the Material-UI hook that uses a CSS-in-JS approach and the makeStyles function:
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
const useStyles = makeStyles({
root: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
},
});
export default function Hook() {
const classes = useStyles();
return <Button className={classes.root}>Hook</Button>;
}
There's also a way to create a custom theme in a separate file and import it where it's needed. Here is an example of the default Theme object and what it contains. It's good to take a look at and get an idea of what can be customized by using createMuiTheme.
Material-UI has multiple approaches to styling and it can be a lot to take in at first. Hope this helps.
tanks a lot about the anwsors. Based in these answers, I develop a helper to separate this kind of props:
import React from 'react'
import styled from "styled-components";
import Button from "#material-ui/core/Button";
import { spacing, sizing } from "#material-ui/system";
const allowedProps = ({props, system}) => {
const notAllowedProps = filterProps({props, system})
const allowedProps = Object.entries(props).reduce((acc, [key, value]) => {
if (!notAllowedProps.includes(key)) {
return {...acc, [key]: value}
}
return acc
}, {})
return allowedProps
}
const filterProps = ({props, system}) => {
const objSystem = system.reduce((acc, element) => ({
...acc,
...element(props)
}), {})
return Object.keys(objSystem)
}
const ButtonBase = (props) => <Button {...allowedProps({props, system: [spacing, sizing]})} />
const MyButton = styled(ButtonBase)...

Using Material-UI System with Default Components

Is it possible to use Material-UI System with the default Material UI components? For example, this currently does not work:
<AppBar bgColor="secondary.main" p={{ xs: 2, sm: 3, md: 4 }}>...</AppBar>
I am wondering, though, is there a way to get this to work (not just for AppBar, but for any Material UI component)? I.e., is there a way to integrate System with the default Material UI components?
If so, how?
Thanks.
The Box component can be used to add the Material-UI System features to other components by using its component prop. One caveat is that if you try to change styling that is explicitly controlled by the component, you may run into specificity issues. For instance the styling of the background color on the Button in my example below doesn't work correctly if you flip the order of the Button and Box imports since this causes the order of their styles in the <head> to also be flipped.
import React from "react";
import styled, { ThemeProvider as SCThemeProvider } from "styled-components";
import { useTheme, StylesProvider } from "#material-ui/core/styles";
import MuiAppBar from "#material-ui/core/AppBar";
import Button from "#material-ui/core/Button";
import Box from "#material-ui/core/Box";
const AppBar = styled(MuiAppBar)`
background-color: red;
${props => props.theme.breakpoints.up("sm")} {
background-color: orange;
}
${props => props.theme.breakpoints.up("md")} {
background-color: yellow;
color: black;
}
${props => props.theme.breakpoints.up("lg")} {
background-color: green;
color: white;
}
`;
export default function App() {
const muiTheme = useTheme();
return (
<StylesProvider injectFirst>
<SCThemeProvider theme={muiTheme}>
<Box component={AppBar} p={{ xs: 2, sm: 3, md: 4, lg: 5 }}>
Sample AppBar 1
</Box>
<div style={{ height: "100px" }} />
<Box component={Button} bgcolor="secondary.main">
Sample Button
</Box>
</SCThemeProvider>
</StylesProvider>
);
}
Related answer: Material-UI Grid does not hide whe use Display

Media Queries in Material-UI Using Styled-Components

Material UI has a nice set of built-in media queries: https://material-ui.com/customization/breakpoints/#css-media-queries
Material UI also allows us to use Styled-Components with Material UI: https://material-ui.com/guides/interoperability/#styled-components
I want to know how to combine the two together. That is, how can I make media queries using Styled Components and Material-UI's built-in breakpoints?
Thanks.
UPDATE:
Here is an example of what I am trying to do:
import React, { useState } from 'react'
import styled from 'styled-components'
import {
AppBar as MuiAppBar,
Drawer as MuiDrawer,
Toolbar,
} from '#material-ui/core'
const drawerWidth = 240
const AdminLayout = ({ children }) => {
return (
<BaseLayout>
<AppBar position="static">
<Toolbar>
TOOLBAR
</Toolbar>
</AppBar>
<Drawer>
DRAWER
</Drawer>
{children}
</BaseLayout>
)
}
AdminLayout.propTypes = {
children: PropTypes.node.isRequired,
}
export default AdminLayout
// ------- STYLES -------
const AppBar = styled(MuiAppBar)`
/* Implement appBar styles from useStyles */
`
const Drawer = styled(MuiDrawer)`
/* Implement drawer styles from useStyles */
`
// STYLES THAT I WANT TO CONVERT TO STYLED-COMPONENTS
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
},
drawer: {
[theme.breakpoints.up('sm')]: {
width: drawerWidth,
flexShrink: 0,
},
},
appBar: {
[theme.breakpoints.up('sm')]: {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: drawerWidth,
},
},
toolbar: theme.mixins.toolbar,
}))
Below is an example showing one way of leveraging the Material-UI theme breakpoints with styled-components. This is passing the Material-UI theme to the styled-components ThemeProvider in order to make it available as a prop within the styles. The example also uses StylesProvider with the injectFirst prop so that the Material-UI styles will occur at the beginning of the <head> rather than the end, so that the styled-components styles occur after the Material-UI styles and therefore win when specificity is otherwise equal.
import React from "react";
import styled, { ThemeProvider as SCThemeProvider } from "styled-components";
import { useTheme, StylesProvider } from "#material-ui/core/styles";
import MuiAppBar from "#material-ui/core/AppBar";
const AppBar = styled(MuiAppBar)`
background-color: red;
${props => props.theme.breakpoints.up("sm")} {
background-color: orange;
}
${props => props.theme.breakpoints.up("md")} {
background-color: yellow;
color: black;
}
${props => props.theme.breakpoints.up("lg")} {
background-color: green;
color: white;
}
`;
export default function App() {
const muiTheme = useTheme();
return (
<StylesProvider injectFirst>
<SCThemeProvider theme={muiTheme}>
<AppBar>Sample AppBar</AppBar>
</SCThemeProvider>
</StylesProvider>
);
}
Related documentation:
styled-components theme usage: https://styled-components.com/docs/advanced#theming
StylesProvider injectFirst: https://material-ui.com/styles/api/#stylesprovider
If you are using the "Style Objects" approach (i.e., "JavaScript") to styled-components, then this is the way to achieve that same outcome. This builds on top of what Ryan Cogswell mentioned earlier.
Some might prefer this if switching over from another CSS-in-JS system (like Material-UI's built-in JSS). Also, the "Style Objects" approach only requires you to bring in props one time as opposed to using the props variable on any line. It's good to have choices. 😇
Style Object
const AppBar = styled(MuiAppBar)((props) => ({
backgroundColor: red;
[props.theme.breakpoints.up("sm")]: {
backgroundColor: orange,
},
[props.theme.breakpoints.up("md")]: {
backgroundColor: yellow,
color: black,
},
[props.theme.breakpoints.up("lg")]: {
backgroundColor: green,
color: white,
},
}));
Style Object, but more concise
Since we only need to access the props one time using the JavaScript approach and we only use theme in this style area, we can destructure theme from the incoming props for a bit less code.
const AppBar = styled(MuiAppBar)(({ theme }) => ({
backgroundColor: red;
[theme.breakpoints.up("sm")]: {
backgroundColor: orange,
},
[theme.breakpoints.up("md")]: {
backgroundColor: yellow,
color: black,
},
[theme.breakpoints.up("lg")]: {
backgroundColor: green,
color: white,
},
}));
Note: If you are using TypeScript and have set up your styled-components theme to match the Material-UI theme, then type safety still works as expected in either the CSS or JavaScript approach.
The breakpoints are provided as part of the default theme.
They are constants and won't change, therefore you can use them across the components or styled themes:
import React from 'react';
import styled from 'styled-components';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles(theme => {
console.log('md', theme.breakpoints.up('md'));
return {};
});
const BP = {
MD: `#media (min-width:960px) `,
};
const Container = styled.div`
background-color: green;
${({ bp }) => bp} {
background-color: red;
}
`;
export default function StyledComponentsButton() {
useStyles();
return <Container bp={BP.MD}>Example</Container>;
}
const StyledDrawer = styled(Drawer)(
({ theme }) => `
.MuiDrawer-paper {
${theme.breakpoints.up('sm')} {
width: 370px;
}
${theme.breakpoints.down('sm')} {
width: 100vw;
}
}
`)
The syntax may look weird, but trying this code will explain everything
const StyledDiv = styled.div`
${({theme}) => {
console.log(theme.breakpoints.up('lg'));
return "";
}}
`;
// you will see in your console
// #media (min-width:1280px)
Once you understand that theme.breakpoints.up('lg') is same as #media (min-width:1280px) everything become obvious. everytime you put theme.breakpoints.up(key) it get replaced with #media... string.

Material-UI. Invalid hook call

I've been following some examples on how to setup Material-UI with React.
Whatever I try, I'm getting back the following error:
Error: Minified React error #321; visit
https://reactjs.org/docs/error-decoder.html?invariant=321 for the full
message or use the non-minified dev environment for full errors and
additional helpful warnings.
This seems to be Hook related, but I can't find any reason why this happens.
The component
Button
import React from 'react';
import { withStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
const styles = {
root: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
},
};
function ButtonComp(props) {
const { classes } = props;
return <Button className={classes.root}>Higher-order component</Button>;
}
export default withStyles(styles)(ButtonComp);
Implemented like this
import * as React from 'react';
import ButtonComp from './ButtonComp';
const App = ({ children }) => {
return (
<React.Fragment>
<ButtonComp />
<main>
{children}
</main>
</React.Fragment>
);
};
export default (App);
I'm running on react#16.11.0 and #material-ui/core#4.5.1.
What am I missing here? I can't find the problem at all.

How do I override compiled classes?

I cam trying to apply override styles to a compiled class name. my compiled code is like...
<div class="MuiListItemText-root-262" >
I want to be able to target that specific item like this
const styles = () => { MultiListItemText-root-262: { color: red; } }
in vanilla CSS I could just do .MultiListItemText-root-262: { color: red; }
How can I do the equivalent in JSS?
You cannot do it this way.
The classname "MuiListItemText-root-262" is dynamic, and the id "262" is not reliable and may change.
Please look at the official documentation of Material UI for using JSS overrides : https://material-ui.com/customization/overrides/
There are several techniques available depending on the level of variation your want to achieve.
For a typical "one time" override, see the first sample code which uses the withStyles HOC
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
// We can inject some CSS into the DOM.
const styles = {
button: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
borderRadius: 3,
border: 0,
color: 'white',
height: 48,
padding: '0 30px',
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
},
};
function ClassNames(props) {
return (
<Button className={props.classes.button}>
{props.children ? props.children : 'class names'}
</Button>
);
}
ClassNames.propTypes = {
children: PropTypes.node,
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(ClassNames);

Resources