How to use 'theme' and 'props' in makeStyles? - reactjs

How do I write makeStyles() so that it allows me to use both theme variables and props?
I've tried this:
const useStyles = makeStyles(theme => ({
appbar: props => ({
boxShadow: "none",
background: "transparent",
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
color: theme.palette.getContrastText(props)
}),
}));
And called it in the component with:
const classes = useStyles(backgroundColor);
Where backgroundColor is a prop on the component with type CSSProperties["backgroundColor"]
But I'm getting the error:
TypeError: Cannot read property 'rules' of undefined
at RuleList.onUpdate (C:\Users\...\node_modules\jss\dist\jss.cjs.js:944:14)
at RuleList.update (C:\Users\...\node_modules\jss\dist\jss.cjs.js:923:12)
at StyleSheet.update (C:\Users\...\node_modules\jss\dist\jss.cjs.js:1178:39)
at attach (C:\Users\...\node_modules\#material-ui\styles\makeStyles\makeStyles.js:141:18)
at C:\Users\...\node_modules\#material-ui\styles\makeStyles\makeStyles.js:262:7
at useSynchronousEffect (C:\Users\...\node_modules\#material-ui\styles\makeStyles\makeStyles.js:207:14)
at C:\Users\...\node_modules\#material-ui\styles\makeStyles\makeStyles.js:254:5
at Layout (C:\Users\...\.next\server\static\development\pages\index.js:1698:17)
at processChild (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:2888:14)
at resolve (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:2812:5)
at ReactDOMServerRenderer.render (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:3202:22)
at ReactDOMServerRenderer.read (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:3161:29)
at renderToString (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:3646:27)
at render (C:\Users\...\node_modules\next-server\dist\server\render.js:86:16)
at renderPage (C:\Users\...\node_modules\next-server\dist\server\render.js:211:20)
at ctx.renderPage (C:\Users\...\.next\server\static\development\pages\_document.js:2404:22)
100 | handleSignUpClick,
101 | backgroundColor
102 | }) => {
> 103 | const classes = useStyles(backgroundColor);
104 | return (
105 | <AppBar className={classes.appbar}>
106 | <Container maxWidth="lg">
edit: I'm using version 4.0.0-beta.1 of material core and styles

Tested with:
"#material-ui/core": "^4.0.0-beta.1",
"#material-ui/styles": "^4.0.0-rc.0",
JavaScript example:
my-component.js
import React from 'react';
import { makeStyles } from '#material-ui/styles';
import { useStyles } from './my-component.styles.js';
const myComponent = (props) => {
const styleProps = { width: '100px', height: '100px' };
const classes = useStyles(styleProps);
return (
<div className={classes.mySelector}></div> // with 100px and height 100px will be applied
)
}
my-component.styles.js
export const useStyles = makeStyles(theme => ({
mySelector: props => ({
display: 'block',
width: props.width,
height: props.height,
}),
}));
TypeScript example:
my-component.ts
import React, { FC } from 'react';
import { makeStyles } from '#material-ui/styles';
import { useStyles } from './my-component.styles.ts';
import { MyComponentProps, StylesProps } from './my-component.interfaces.ts';
const myComponent: FC<MyComponentProps> = (props) => {
const styleProps: StylesProps = { width: '100px', height: '100px' };
const classes = useStyles(styleProps);
return (
<div className={classes.mySelector}></div> // with 100px and height 100px will be applied
)
}
my-component.interfaces.ts
export interface StyleProps {
width: string;
height: string;
}
export interface MyComponentProps {
}
my-component.styles.ts
import { Theme } from '#material-ui/core';
import { makeStyles } from '#material-ui/styles';
import { StyleProps } from './my-components.interfaces.ts';
export const useStyles = makeStyles<Theme, StyleProps>((theme: Theme) => ({
mySelector: props => ({ // props = { width: string; height: string }
display: 'block',
width: props.width,
height: props.height,
}),
}));
Update
Re-tested with
"#material-ui/core": "^4.12.X"

You need to pass an object to useStyles rather than a string.
So instead of:
const classes = useStyles(backgroundColor);
you should have:
const classes = useStyles(props);
or
const classes = useStyles({backgroundColor});
Then you can get at backgroundColor using:
color: theme.palette.getContrastText(props.backgroundColor).
Here's a working example:
https://codesandbox.io/s/o7xryjnmly

You can do this: (i dont know if is the best way but works... also can access to the theme ang globals provider (using themeProvider))
import { makeStyles }from '#material-ui/core/styles'
import styles from './styles';
const useStyles = makeStyles(theme => (styles(theme))); // here call styles function imported from styles.js
const SideNav = ({drawerState, toggleDrawer}) => {
const classes = useStyles();
return (
<Box className={classes.root}>
<Drawer className="drawer" anchor="left" open={drawerState} onClose={() => toggleDrawer(false)}>
<NavList></NavList>
</Drawer>
</Box>
);
export default SideNav;
and in styles.js
const styles = (theme) => {
return ({
root: {
'& .drawer': {
backgroundColor:'red'
}
}
});
}
export default styles;
makeStyles get the theme by params
so you can create a styles.js for every component with a arrow function inside and
calling from makeStyles that can access to the theme provider.

We have 2 modes in general, your prop variable is imported to the component or not.
If your prop variable is imported, it is a global variable, so it is valid in makeStyles():
import {prop} from ...
const useStyles = makeStyles((theme) => ({
className:{
// use prop
}
})
define component...
If your prop variable is defined in the component (such as a state), you have 2 choices:
You can pass the prop variable to makeStyles():
const useStyles = makeStyles((theme,prop) => ({
className:{
// use prop
}
})
define component...
You can use arrow function without passing any argument (except theme) to makeStyles():
const useStyles = makeStyles((theme) => ({
className:(prop)=>({
//use prop
})
})
define component...

Here is an example of how you can use only props or props and theme both with makeStyles() just like styled-components
component.js
import { tableCellStyling } from './component.styled.js';
const RenderRow = (props) => {
const { noPaddingTopBottom } = tableCellStyling(props);
return(
<StyledTableRow>
{data.map( (row,i) => (
<StyledTableCell className={noPaddingTopBottom}>
{row.data}
</StyledTableCell>
)}
</StyledTableRow>
)
};
Assuming my props object which is being passed by RenderRow Component to tableCellStyling has { color: 'grey', thinRow: true } in it
component.styled.js
import { makeStyles } from '#material-ui/core/styles';
export const tableCellStyling = makeStyles(theme => ({
noPaddingTopBottom: {
borderBottom: ({ color }) => color ? `2px solid ${color}` : '2px solid red',
paddingBottom: props => props.hasActions && 0,
paddingTop: props => props.hasActions && 0,
backgroundColor: theme.palette.common.white,
},
}));

The way you can do it is like this:
import { makeStyles, createStyles, Theme } from "#material-ui/core/styles";
...
...
const classes = useStyles();
...
...
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: propName => ({
border: "none",
boxShadow: "none",
cursor: propName ? "pointer" : "auto",
width: "100%",
backgroundColor: "#fff",
padding: "15px 15px"
}),
updated: {
marginTop: 12,
fontWeight: 400,
color: "#939393"
}
})
);
You can use the prop name inside the element you are styling by making it an arrow function that returns the styling. In addition, you can also style other elements inside the createStyles hook. This took me some time, I hope anyone finds it useful. ✨🔥

Related

react mui custom theme issue with styled component and typescript

I'm porting to typescript this awesome project
https://github.com/codingmonk-yt/chat-app
it's a boilerplate starter for a modern chat webapp
built with material ui, where it customize extensively the default material theme
i'm not very familiar with react and material ui, truth be told, so i don't understand what am i doing wrong, i have a typescript issue when i try to migrate a styled component with its typescript version
ERROR :
Property 'theme' is missing in type '{ children: Element[]; sx: { bgcolor?: string | undefined; border?: solid 2px ${string} | undefined; boxShadow?: inset 0 4px 8px 0 ${string} | undefined; }; }' but required in type '{ theme: CustomTheme; }'.ts(2741)
the error suggests that the component can't see that BoxStyle is wrapped around a themeProvider with my custom theme, but i don't understand why
(i omitted to paste SettingsDrawer component since for now it's nothing more than a container for
SettingColorPresets)
Thank you in advance for any pointers
i'm pasting here all the relevant code, please let me know if you need any more information
/*********************************/
//App.tsx:
/*********************************/
function App() {
return (
<ThemeProvider>
<ThemeSettings>
{" "}
<Routes />{" "}
</ThemeSettings>
</ThemeProvider>
);
}
/*********************************/
//theme.d.ts:
/*********************************/
declare module '#mui/material/styles' {
interface CustomTheme extends Theme {
customShadows: CustomShadows;
palette: CustomPalette;
}
// allow configuration using `createTheme`
interface CustomThemeOptions extends ThemeOptions {
customShadows: CustomShadows;
palette: CustomPalette;
}
export function createTheme(options?: CustomThemeOptions): CustomTheme;
}
/*********************************/
//ThemeProvider.tsx:
/*********************************/
import {
createTheme,
ThemeProvider as MUIThemeProvider,
StyledEngineProvider,
CustomThemeOptions,
ThemeOptions,
CustomTheme,
} from "#mui/material/styles";
export default function ThemeProvider({ children }: { children: ReactNode }) {
const { themeMode, themeDirection } = useSettings();
const isLight = themeMode === "light";
const themeOptions = useMemo(
() => ({
palette: isLight ? palette.light : palette.dark,
typography,
breakpoints,
shape: { borderRadius: 8 },
direction: themeDirection,
shadows: isLight ? shadows.light : shadows.dark,
customShadows: isLight ? customShadows.light : customShadows.dark,
} as CustomThemeOptions),
[isLight, themeDirection]
);
const theme = createTheme(themeOptions);
// if i inspect theme in vscode it shows that is instance of CustomTheme, as it should be...
theme.components = componentsOverride(theme);
return (
<StyledEngineProvider injectFirst>
<MUIThemeProvider theme={theme}>
<CssBaseline />
{children}
</MUIThemeProvider>
</StyledEngineProvider>
);
}
/*********************************/
//ThemeSettings.tsx:
/*********************************/
export default function ThemeSettings({ children }: { children: ReactNode }) {
return (
<ThemeColorPresets>
<ThemeContrast>
<ThemeLocalization>
<ThemeRtlLayout>
{children}
<SettingsDrawer />
</ThemeRtlLayout>
</ThemeLocalization>
</ThemeContrast>
</ThemeColorPresets>
);
}
/*********************************/
//ThemeColorPresets.tsx:
/*********************************/
import PropTypes from "prop-types";
import { ReactNode, useMemo } from "react";
// #mui
import {
alpha,
ThemeProvider,
createTheme,
useTheme,
CustomTheme,
} from "#mui/material/styles";
// hooks
import useSettings from "../../hooks/useSettings";
//
import componentsOverride from "../../theme/overrides";
// ----------------------------------------------------------------------
ThemeColorPresets.propTypes = {
children: PropTypes.node,
};
export default function ThemeColorPresets({
children,
}: {
children: ReactNode;
}) {
const defaultTheme = useTheme<CustomTheme>();
const { setColor } = useSettings();
const themeOptions = useMemo(
() => ({
...defaultTheme,
palette: {
...defaultTheme.palette,
primary: setColor,
},
customShadows: {
...defaultTheme.customShadows,
primary: `0 8px 16px 0 ${alpha(setColor.main, 0.24)}`,
},
}),
[setColor, defaultTheme]
);
const theme = createTheme(themeOptions);
// if i inspect theme in vscode it shows that is instance of CustomTheme, as it should be...
theme.components = componentsOverride(theme);
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
}
Finally, the part with the error:
/*********************************/
//SettingsColorPresets.tsx (CALLED FROM INSIDE <SettingsDrawer />, from ThemeSettings.tsx:
/*********************************/
const BoxStyle = styled(CardActionArea)(({ theme }: { theme: CustomTheme }) => ({
height: 48,
display: "flex",
alignItems: "center",
justifyContent: "center",
color: theme.palette.text.disabled,
border: `solid 1px ${(theme).palette.grey[500_12]}`,
borderRadius: Number(theme.shape.borderRadius) * 1.25,
}));
export default function SettingColorPresets() {
const { themeColorPresets, onChangeColor, colorOption } = useSettings();
return (
<RadioGroup
name="themeColorPresets"
value={themeColorPresets}
onChange={onChangeColor}
>
<Grid dir="ltr" container spacing={1.5}>
{colorOption.map((color: ColorOption) => {
const colorName = color.name;
const colorValue = color.value;
const isSelected = themeColorPresets === colorName;
return (
<Grid key={colorName} item xs={4}>
<BoxStyle <--- ERROR HERE
sx={{
...(isSelected && {
bgcolor: alpha(colorValue, 0.08),
border: `solid 2px ${colorValue}`,
boxShadow: `inset 0 4px 8px 0 ${alpha(colorValue, 0.24)}`,
}),
}}
>
.....

Styled MUI Drawer not opening when variant is temporary

I've created a styled MuiDrawer component so I can add some custom styling the component. I want to use the temporary variant but the Drawer is not opening. When I set the Drawer variant to permanent the Drawer does show. So it's probably the passing of the open prop that is causing the error. When I use the default Drawer component from MUI the temporary variant does work:
// demo.tsx
import * as React from 'react';
// import Drawer from '#mui/material/Drawer';
import {Drawer} from './styles';
import Button from '#mui/material/Button';
export default function TemporaryDrawer() {
const [open, setOpen] = React.useState(false);
const toggleDrawer = () => {
setOpen(!open);
};
return (
<>
<Button onClick={toggleDrawer}>Toggle Drawer</Button>
<Drawer
variant='temporary'
open={open}
onClose={toggleDrawer}
>
<p>Drawer</p>
</Drawer>
</>
);
}
// styles.tsx
import {styled} from '#mui/material';
import {Theme, CSSObject} from '#mui/material/styles';
import MuiDrawer from '#mui/material/Drawer';
const drawerWidth = 240;
const openedMixin = (theme: Theme): CSSObject => ({
backgroundColor: 'green',
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: 'hidden',
});
const closedMixin = (theme: Theme): CSSObject => ({
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
overflowX: 'hidden',
width: `calc(${theme.spacing(7)} + 1px)`,
[theme.breakpoints.up('sm')]: {
width: `calc(${theme.spacing(8)} + 1px)`,
},
});
export const Drawer = styled(MuiDrawer, {shouldForwardProp: (prop) => prop !== 'open'})(
({theme, open}) => ({
width: drawerWidth,
flexShrink: 0,
whiteSpace: 'nowrap',
boxSizing: 'border-box',
...(open && {
...openedMixin(theme),
'& .MuiDrawer-paper': openedMixin(theme),
}),
...(!open && {
...closedMixin(theme),
'& .MuiDrawer-paper': closedMixin(theme),
}),
}),
)
https://codesandbox.io/s/temporarydrawer-material-demo-forked-zci40?file=/demo.tsx
While #v1s10n_4 answer is correct, I'll explain a bit more the reason.
The purpose of the shouldForwardProp callback is to prevent the styling props created by the HOC from leaking to the DOM element leading to invalid attribute error. Your Drawer has an open prop, it's a known prop of Dialog so you don't need to be concerned about the prop is not handled properly here:
const Dialog = (props) => {
// I know this open prop, so I'm gonna extract it here
const { open, ...other } = props
// and do some logic with the open prop
// and make sure it is not passed to the Component
return <Component {...other} />
}
However, if you pass an arbitrary prop that is not from the Dialog API, like bgcolor for example:
<Dialog bgcolor='red'
Then it will be passed down to the DOM element:
const Dialog = (props) => {
const { open, ...other /* other includes the bgcolor prop */ } = props
// logic...
return <Component {...other} />
}
When you are using styled to create a styled component:
const StyledDialog = styled(Dialog)(...)
<StyledDialog bgcolor='red'
It'd look like this behind the scene:
const StyledDialog = (props) => {
const className = getStyles(props);
return <Dialog {...props} className={className} />
}
That's why you need to use shouldForwardProp, to filter out the style-related props (not the Dialog props) so it won't be passed down to the Dialog:
const StyledDialog = (props) => {
const { bgcolor, ...other } = props;
const className = getStyles(props);
// bgcolor is filtered now.
return <Dialog {...other} className={className} />
}
you can remove {shouldForwardProp: (prop) => prop !== 'open'} in your styled Drawer definition.
codesandbox

how to do Storybook React Material UI custom styling

Hello I'm new to stroybook and and would liek to use custom colors on my component but i keep on getting errors.
This is the code below, could sb help me to fix this so I can see how to use storybook material ui elements in storybook..
the code:
import "bootstrap/dist/css/bootstrap.min.css";
import React from "react";
import { deepOrange, deepPurple } from '#material-ui/core/colors';
import { makeStyles } from '#material-ui/core/styles';
import Avatar from '#material-ui/core/Avatar';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
'& > *': {
margin: theme.spacing(1),
},
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
},
purple: {
color: theme.palette.getContrastText(deepPurple[500]),
backgroundColor: deepPurple[500],
},
}));
And this is the error.
export default {
const classes = useStyles();
title: "Components/Avatar/Number",
component: Avatar
}
export const _Avatar = () => (
<>
<Avatar>H</Avatar>
<Avatar className={classes.orange}>N</Avatar>
<Avatar className={classes.purple}>OP</Avatar>
</>
);
ERROR in
src\stories\Avatars\avatar.stories.js
Parsing error: Unexpected keyword 'const'.
export default {
const classes = useStyles();
^
title: "Components/Avatar/Number",
component: Avatar
}
It is syntactically incorrect to use the const keyword and the = operator inside your default export object. Try the following code instead:
export default {
classes: useStyles(),
title: "Components/Avatar/Number",
component: Avatar
}

Pass props to classes when using Typescript with Material-UI useStyles

I'm converting a project from js to Typescript, and in one component I pass props to the material-ui theme to change it based on input.
Relevant sections of the react functional component are included below:
import React from 'react';
import {makeStyles} from "#material-ui/core/styles";
// #ts-ignore
import {SketchPicker} from 'react-color';
import InputAdornment from "#material-ui/core/InputAdornment";
const useStyles = makeStyles(theme => ({
colorSwatch: props => ({
width: theme.spacing(5),
height: 30,
border: '1px solid black',
borderRadius: theme.spacing(0.25),
backgroundColor: props.embedColor,
}),
}));
export default function FormFragmentEmbed(props: any) {
const { register, errors } = props.form;
const { defaultValues } = props;
const [colorPicker, setColorPicker] = React.useState<string>(defaultValues.embedColor);
const classes = useStyles({
embedColor: watchColor,
});
return (
<TextField
name={'embedColor'}
label={'Embed Color'}
defaultValue={colorPicker}
error={errors.embedColor !== undefined}
inputRef={register({})}
InputProps={{
endAdornment: <InputAdornment position="end"><Box className={classes.colorSwatch}/></InputAdornment>
}}
/>
}
But the line where I define backgroundColor in makeStyles gives me an error
TS2339: Property 'embedColor' does not exist on type '{}'.
How do I fix that error?
 
Also semi-related, as you can see I added a linting line to ignore the import for SketchPicker because TS was spitting out an error when I tried to import that. What would be the proper way to import that so it wouldn't cause an error?
Try using this syntax,
const useStyles = makeStyles({
colorSwatch: props => ({
width: theme.spacing(5),
height: 30,
border: '1px solid black',
borderRadius: theme.spacing(0.25),
backgroundColor: props.embedColor,
}),
});
export default function FormFragmentEmbed(props: any) {
const { register, errors } = props.form;
const { defaultValues } = props;
const [colorPicker, setColorPicker] = React.useState<string>(defaultValues.embedColor);
const classes = useStyles(defaultValues);
return (
<TextField
name={'embedColor'}
label={'Embed Color'}
defaultValue={colorPicker}
error={errors.embedColor !== undefined}
inputRef={register({})}
InputProps={{
endAdornment: <InputAdornment position="end"><Box className={classes.colorSwatch}/></InputAdornment>
}}
/>
}
Now your code should work fine I guess. Official documentation link to use props with makeStyles and useStyles.

How to pass className style to sub component in `material-ui`?

Material UI uses className for stying. But how can I pass the style to sub react component?
Below is my style definition.
const styles = createStyles({
root: {
backgroundColor: 'transparent !important',
boxShadow: 'none',
paddingTop: '25px',
color: '#FFFFFF'
},
subComponentStyle: {
...
}
});
And I use this like:
...
const NavigationBar = (props) => {
const { classes } = props;
return (
<div className={classes.root}>
// Add other code here
<SubComponent ... > // how to pass `classes.subComponentStyle` style here
</div>
)
}
...
export default withStyles(styles)(NavigationBar);
If the SubComponent component is also exported with withStyles. How can I pass some styles to override its own styling?
My SubComponent is exported as:
const styles = createStyles({
...
});
const SubComponent = ({classes}) => {
...
}
export default withStyles(styles)(SubComponent);
as you can see, it has its own classes. I don't want to override its classes completely. Is there a way to merge the passed in classes with its internal classes?
// Edited to merged styles
MUI will merge styles if you pass the classes as well as wrap the child withStyles. ie:
import { styles } from './NavStyles'
const NavigationBar = (props) => {
const { classes } = props;
return (
<div className={classes.root}>
<SubComponent classes={classes} >
</div>
)
};
export default withStyles(styles)(NavigationBar);
and in then also apply styles to the child component
import { styles } from './SubCompStyles'
const SubComponent = ({classes}) => {
// classes object is a merge of both parent and child styles
// ... component logic
};
export default withStyles(styles)(SubComponent)
Heres how you can do it with hook API:
Sub component
const useStyles = makeStyles((theme) => ({
root: {
borderRadius: 3,
color: 'white',
padding: '0 30px',
width: '12em',
height: 43,
borderRadius: 21.5,
textTransform: 'capitalize',
... your styles here.
},
}))
export default function AuthSecondaryButton(props) {
const classes = useStyles()
console.log('s', props.className)
return (
<Button
{...props}
className={clsx({
[classes.root]: true,
[props.className]: true,
})}
/>
)
}
Parent component
const useStyles = makeStyles((theme) => ({
secondaryButton: {
marginTop: theme.spacing(1),
},
}))
export default function App(props) {
const classes = useStyles()
return(
<AuthSecondaryButton
onClick={onClickSecondaryButton}
className={classes.secondaryButton}
>
Sign Up
</AuthSecondaryButton>
)
A slight tweak to #clever_usernames approach.
This uses classnames package instead of the clsx package, which we use in our project.
Replacing this...
className={clsx({
[classes.root]: true,
[props.className]: true,
})}
with this...
className={classNames(classes.root, props.className)}
Full Example
Sub component
import classNames from 'classnames'
const useStyles = makeStyles((theme) => ({
root: {
borderRadius: 3,
color: 'white',
padding: '0 30px',
width: '12em',
height: 43,
borderRadius: 21.5,
textTransform: 'capitalize',
... your styles here.
},
}))
export default function AuthSecondaryButton(props) {
const classes = useStyles()
console.log('s', props.className)
return (
<Button
{...props}
className={classNames(classes.root, props.className)}
/>
)
}
Parent component
const useStyles = makeStyles((theme) => ({
secondaryButton: {
marginTop: theme.spacing(1),
},
}))
export default function App(props) {
const classes = useStyles()
return(
<AuthSecondaryButton
onClick={onClickSecondaryButton}
className={classes.secondaryButton}
>
Sign Up
</AuthSecondaryButton>
)

Resources