When I implement a simple React component with Mui's withStyles HOC, I have to use the keyword "default" when exporting the component. Why can't I use the HOC in the return statement within the functional component?
Is there something about Js or ReactJs that I'm missing?
Since I am forced to export this component as default, I lose the possibility to use the named import functionality, without using another import/export layer in between.
Below is the current working code:
// Card.js
import React from "react";
import {
Card,
withStyles
} from "#material-ui/core";
const styles = theme => ({
card: {
margin: theme.spacing(2)
}
});
function CustomCard(props) {
const {classes} = props;
return (
<Card className={classes.card}>
Export me without being the default component.
</Card>
);
}
export default withStyles(styles)(MediaCard);
// Elsewhere.js
import CustomCard from "Card";
...
But i'd rather write something like this:
// Cards.js
import React from "react";
import {
Card,
withStyles
} from "#material-ui/core";
const styles = theme =\> ({
card: {
margin: theme.spacing(2)
},
anotherCard: {
margin: theme.spacing(4)
}
});
export function CustomCard(props) {
const {classes} = props;
return withStyles(styles)(
<Card className={classes.card}>
Jeah. I'm not the default component.
</Card>
);
}
export function AnotherCard(props) {
const {classes} = props;
return withStyles(styles)(
<Card className={classes.anotherCard}>
Jeah. I'm not the default component either.
</Card>
);
}
// Elsewhere.js
import { CustomCard, AnotherCard } from "Cards";
...
You can do it the way you want to but you to change the way you define your components. The technical reason is that all exports except default need to be named, otherwise you can't import them and know what's what. Since withStyles() returns a statement and not a named variable/function you can't export it without a name.
export const AnotherCard = withStyles(styles)((props) => {
const {classes} = props;
return (
<Card className={classes.anotherCard}>
Jeah. I'm not the default component either.
</Card>
);
});
The downside of this is of course now your components aren't hoisted.
Using route-based code splitting causes css duplicate in chunks.
Steps to reproduce:
Create simple app with create-react-app
Use react-router-dom for client side routing
Add one common component with local css.
Add 2 pages, using the common component with style overwriting
Run build command
Check static folder
// /components/CommonPageHeader/CommonPageHeader.module.scss
.header {
color: black;
}
// /components/CommonPageHeader/CommonPageHeader.tsx
import styles from "./CommonPageHeader.module.scss";
import { ReactNode } from "react";
export const CommonPageHeader = (props: {
className: string;
children: ReactNode;`your text`
}) => {
const { className = "", children } = props;
return <h1 className={`${styles.header} ${className}`}>{children}</h1>;
};
// /pages/Home/Home.module.scss
.greenHeader {
color: green;
}
// /pages/Home/Home.tsx
import React from "react";
import { Link } from "react-router-dom";
import { CommonPageHeader } from "../../components/CommonPageHeader/CommonPageHeader";
import styles from "./Home.module.scss";
const Home = () => (
<>
<CommonPageHeader className={styles.greenHeader}>
I'm a green header
</CommonPageHeader>
<Link to={"some-page"}>Some page</Link>
</>
);
export default Home;
// /pages/SomePage/SomePage.module.scss
.redHeader {
color: red;
}
// /pages/SomePage/SomePage.tsx
import React from "react";
import { Link } from "react-router-dom";
import { CommonPageHeader } from "../../components/CommonPageHeader/CommonPageHeader";
import styles from "./SomePage.module.scss";
const SomePage = () => {
return (
<>
<CommonPageHeader className={styles.redHeader}>
I'm a red header
</CommonPageHeader>
<Link to={"/"}>Home</Link>
</>
);
};
export default SomePage;
// index.tsx
import React, { Suspense } from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
const Home = React.lazy(() => import("./pages/Home/Home"));
const SomePage = React.lazy(() => import("./pages/SomePage/SomePage"));
const router = createBrowserRouter([
{
path: "/",
element: (
<Suspense>
<Home />
</Suspense>
),
},
{
path: "/some-page",
element: (
<Suspense>
<SomePage />
</Suspense>
),
},
]);
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
You get css chunks with duplicates and unexpected styles overwrites in production:
// /build/static/css/499.408c3ff0.chunk.css
.CommonPageHeader_header__eTauC {
color: #000
}
.Home_greenHeader__YwF4H {
color: green
}
// /build/static/css/136.855973ab.chunk.css
.CommonPageHeader_header__eTauC {
color: #000
}
.SomePage_redHeader__a-I8c {
color: red
}
Demo page
Click to some page link
Go back to home page
Repo
Is there way to prevent it?
Hey how are you? i'm using nextjs / styled components and i would like to change a string via props. I made a layout component with a main and it has an props to make it dinamic per page
Page 'prueba.js'
import React from 'react';
import Layout from '../components/Layout/Layout';
const prueba = () => {
const setNewBackground = 'green';
console.log(setNewBackground)
return (
<Layout setNewBackground={setNewBackground}>
<p>holaaaaa</p>
</Layout>
);
}
export default prueba;
Layout.js component
import React,{useState} from 'react';
import Main from './Main';
const Layout = props => {
const [background, setBackground] = useState('blue')
const setNewBackground = () => {
setBackground (background);
}
return (
<>
<Main newBackground={background}>
{props.children}
</Main>
</>
);
}
export default Layout;
And Main.js component
import styled from '#emotion/styled';
const Main = styled.main`
background:${props => props.newBackground};
height:100vh;
width:100%;
`;
export default Main;
I check it in console but it shows me undefined. Wheres the error :(? thanks and have a good year!
You don't need to create a state for that. You can use only the color prop you passed to the Layout.
Prueba.js
import React from 'react';
import Layout from '../components/Layout/Layout';
const prueba = () => {
return (
<Layout backgroundColor='green'>
<p>holaaaaa</p>
</Layout>
);
}
export default prueba;
Layout.js
import React,{useState} from 'react';
import Main from './Main';
const Layout = ({ backgroundColor, chidlren }) => {
return (
<Main backgroundColor={backgroundColor}>
{children}
</Main>
);
}
export default Layout;
Main.js
import styled from '#emotion/styled';
const Main = styled.main`
background:${({ backgroundColor }) => backgroundColor || 'your default color'};
height:100vh;
width:100%;
`;
export default Main;
I am trying to create a background theme which will switch on onClick. On onClick it must change the background color of body in react app. I've managed to implement useContext, and now it toggles and changes the list items color in Header component. How to set it to body as well? Any help will be appreciated.
Here is my useContext color component
import React from 'react'
export const themes = {
light: {
foreground: '#ffffff',
},
blue: {
foreground: 'blue',
},
}
export default React.createContext({
theme: themes.light,
switchTheme: () => {},
})
onClick Button component
import React, { useContext } from 'react'
import ThemeContext from './context'
import './ThemedButton.scss'
const ThemedButton = () => {
const { switchTheme } = useContext(ThemeContext)
return (
<>
<button className="btn" onClick={switchTheme}>
Switch
</button>
</>
)
}
export default ThemedButton
App.js
import React, { useState } from 'react'
import SearchBar from './components/SearchBar';
import useCountries from './Hooks/useCountries';
import MainTable from './components/MainTable';
import ThemeButton from './useContext/ThemedButton';
import ThemeContext from './useContext/context';
import { searchProps } from './types';
import { themes } from './useContext/context';
import Routes from './Routes';
import './App.scss'
export default function App() {
const [search, setSearch] = useState('')
const [data] = useCountries(search)
const [context, setContext] = useState({
theme: themes.light,
switchTheme: () => {
setContext((current) => ({
...current,
theme: current.theme === themes.light ? themes.blue : themes.light,
}))
},
})
const handleChange: React.ReactEventHandler<HTMLInputElement> = (e): void => {
setSearch(e.currentTarget.value)
}
return (
<div className="App">
<SearchBar handleChange={handleChange} search={search as searchProps} />
<ThemeContext.Provider value={context}>
<ThemeButton />
<MainTable countries={data} />
</ThemeContext.Provider>
<Routes />
</div>
)
}
Header component
import React, { useContext } from 'react'
import ThemeContext from '../../useContext/context'
import './Header.scss'
export default function Header() {
const { theme } = useContext(ThemeContext)
return (
<div className="header">
<ul className="HeadtableRow" style={{ color: theme.foreground }}> // here it's set to change list items color
<li>Flag</li>
<li>Name</li>
<li>Language</li>
<li>Population</li>
<li>Region</li>
</ul>
</div>
)
}
If you want to change your body tag in your application you need to modify DOM and you can add this code to your Header.js (or any other file under your context) file:
useEffect(() => {
const body = document.getElementsByTagName("body");
body[0].style.backgroundColor = theme.foreground
},[])
*** Don't forget to import useEffect
*** Inline style like below is a better approach than modifying DOM directly
<div className="App" style={{backgroundColor: context.theme.foreground}}>
//For under context files just use theme.foreground
<SearchBar handleChange={handleChange} search={search as searchProps} />
<ThemeContext.Provider value={context}>
<ThemeButton />
<MainTable countries={data} />
</ThemeContext.Provider>
<Routes />
</div>
I want to use useStyle to style the Class Component . But this can be easily done hooks. but i want to use Component instead. But I cant figure out how to do this.
import React,{Component} from 'react';
import Avatar from '#material-ui/core/Avatar';
import { makeStyles } from '#material-ui/core/styles';
import LockOutlinedIcon from '#material-ui/icons/LockOutlined';
const useStyles = makeStyles(theme => ({
'#global': {
body: {
backgroundColor: theme.palette.common.white,
},
},
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
}
}));
class SignIn extends Component{
const classes = useStyle(); // how to assign UseStyle
render(){
return(
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
</div>
}
}
export default SignIn;
You can do it like this:
import { withStyles } from "#material-ui/core/styles";
const styles = theme => ({
root: {
backgroundColor: "red"
}
});
class ClassComponent extends Component {
state = {
searchNodes: ""
};
render() {
const { classes } = this.props;
return (
<div className={classes.root}>Hello!</div>
);
}
}
export default withStyles(styles, { withTheme: true })(ClassComponent);
Just ignore the withTheme: true if you aren't using a theme.
To get this working in TypeScript, a few changes are needed:
import { createStyles, withStyles, WithStyles } from "#material-ui/core/styles";
const styles = theme => createStyles({
root: {
backgroundColor: "red"
}
});
interface Props extends WithStyles<typeof styles>{ }
class ClassComponent extends Component<Props> {
// the rest of the code stays the same
for class Components you can use withStyles instead of makeStyles
import { withStyles } from '#material-ui/core/styles';
const useStyles = theme => ({
fab: {
position: 'fixed',
bottom: theme.spacing(2),
right: theme.spacing(2),
},
});
class ClassComponent extends Component {
render() {
const { classes } = this.props;
{/** your UI components... */}
}
}
export default withStyles(useStyles)(ClassComponent)
Hey I had a similar problem. I solved it by replacing makeStyles with withStyles and then at the point where do something like const classes = useStyle();, replace that with const classes = useStyle;
You notice useStyle is not supposed to be a function call but rather a variable assignment.
That should work fine after you've made those changes.
useStyles is a react hook. You can use it in function component only.
This line creates the hook:
const useStyles = makeStyles(theme => ({ /* ... */ });
You are using it inside the function component to create classes object:
const classes = useStyles();
Then in jsx you use classes:
<div className={classes.paper}>
Suggested resources:
https://material-ui.com/styles/basics/
https://reactjs.org/docs/hooks-intro.html
Like other answers already stated you should use withStyles to augment a component and pass the classes through the properties. I've taken the liberty to modify the Material-UI stress test example into a variant that uses a class component.
Note that the withTheme: true option is normally not needed when you simply want to use the styles. It is needed in this example because the actual value of the theme is used in the render. Setting this option makes theme available through the class properties. The classes prop should always be provided, even if this option is not set.
const useStyles = MaterialUI.withStyles((theme) => ({
root: (props) => ({
backgroundColor: props.backgroundColor,
color: theme.color,
}),
}), {withTheme: true});
const Component = useStyles(class extends React.Component {
rendered = 0;
render() {
const {classes, theme, backgroundColor} = this.props;
return (
<div className={classes.root}>
rendered {++this.rendered} times
<br />
color: {theme.color}
<br />
backgroundColor: {backgroundColor}
</div>
);
}
});
function StressTest() {
const [color, setColor] = React.useState('#8824bb');
const [backgroundColor, setBackgroundColor] = React.useState('#eae2ad');
const theme = React.useMemo(() => ({ color }), [color]);
const valueTo = setter => event => setter(event.target.value);
return (
<MaterialUI.ThemeProvider theme={theme}>
<div>
<fieldset>
<div>
<label htmlFor="color">theme color: </label>
<input
id="color"
type="color"
onChange={valueTo(setColor)}
value={color}
/>
</div>
<div>
<label htmlFor="background-color">background-color property: </label>
<input
id="background-color"
type="color"
onChange={valueTo(setBackgroundColor)}
value={backgroundColor}
/>
</div>
</fieldset>
<Component backgroundColor={backgroundColor} />
</div>
</MaterialUI.ThemeProvider>
);
}
ReactDOM.render(<StressTest />, document.querySelector("#root"));
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/#material-ui/core#4/umd/material-ui.production.min.js"></script>
<div id="root"></div>
Yet another way to do this, albeit a bit of a workaround.
Some may say this doesn't really answer the question, but I would argue that it does. The end result is useStyles() delivers the styling for a class-based yet multi-part parent component.
In my case I needed a standard Javascript class export so that I could call new MyClass() without a MyClass is not a constructor error.
import { Component } from "./react";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
someClassName: {
...
}
}));
export default class MyComponent extends Component {
render() {
return <RenderComponent {...this.props} />;
}
}
function RenderComponent(props) {
const classes = useStyles();
return (
/* JSX here */
);
}
how to add multiple classes in ClassName with class component
import { withStyles } from "#material-ui/core/styles";
const styles = theme => ({
root: {
backgroundColor: "red"
},
label: {
backGroundColor:"blue"
}
});
class ClassComponent extends Component {
state = {
searchNodes: ""
};
render() {
const { classes } = this.props;//
return (
<div className={classes.root + classes.label}>Hello!</div> //i want to add label style also with
);
}
}