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

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>
)

Related

MUI v5 passing props to CSS theme using styled()

Previously, in Material-UI v4, I had this bit of code
const { customPadding } = props;
const classes = useStyles({
padding: customPadding,
} as any);
To pass props to the classes of an element.
But v5 uses emotion instead of JSS, where I do something like this instead.
const StyledContainer = styled(Container)(({theme}: any) => ({
[`&.${classes.FullPageLayoutRoot}`]: (props: any) => ({
minHeight: `calc(100vh - ${appBarHeight}px - ${theme.spacing(1)} - 1px)`,
display: 'flex',
}),
[`&.${classes.middle}`]: {
alignItems: 'center',
},
[`& .${classes.paper}`]: (props: any) => ({
backgroundColor: grey[800],
marginBottom: theme.spacing(1),
padding: theme.spacing(props.padding),
minWidth: '100%',
})
}));
...
return(
<StyledContainer maxWidth={maxWidth} fixed className={clsx(classes.FullPageLayoutRoot, {
[classes.middle]: middle,
})}>
<Paper className={clsx(classes.paper, classes.padding, className)} {...PaperProps} >
{content}
</Paper>
</StyledContainer>
)
How would I accomplish this in Material-UI v5?
They're passed along with the theme property in the callback:
const MyDiv = styled("div", {
// indicate that this prop name should be used to conditionally
// style the component only and should not be spread to the DOM element.
shouldForwardProp: (propName) => propName !== "isRed"
})(({ theme, isRed }) => ({
backgroundColor: isRed ? "red" : theme.palette.primary.main
}));
export default function ThemeUsage() {
return (
<MyDiv isRed>Styled div with theme</MyDiv>
);
}
Live Demo

How do customise a component's CSS selectors in FluentUI

I am trying to customise the behavior of an FluentUI component when it is hovered over (A Primary Button in this case). How do I customise CSS selectors when I am using Microsoft's React FluentUI library.
I tried this initially (This approach is deprecated now, in favor of the method where you add selectors as siblings)...
export const MyButton = (props: IButtonProps) => {
const styles: IButtonStyles = {
root: {
backgroundColor: '#000000',
selectors : {
':hover': {
backgroundColor: '#0000ff'
}
}
},
}
return (
<PrimaryButton styles={styles} {...props} />
);
}
Then I tried this:
export const MyButton = (props: IButtonProps) => {
const styles: IButtonStyles = {
root: {
backgroundColor: '#000000',
':hover': {
backgroundColor: '#0000ff'
}
},
}
return (
<PrimaryButton styles={styles} {...props} />
);
}
Both approaches do not seem to be working. Am I missing something?
With new FluentUI you can modify styles trough specific props based on button state:
export const MyButton = (props: IButtonProps) => {
const styles: IButtonStyles = {
root: {
backgroundColor: '#000000',
},
rootHovered: {
backgroundColor: '#0000ff',
},
}
return (
<PrimaryButton styles={styles} {...props} />
);
}
Codepen working example.
On this link you have IButtonStyles interface.

TypeError: theme is undefined - When trying to render Material UI component

I am having trouble rendering my react component since I separated my jss file and changed it from makeStyles to withStyles to avoid a hook problem.
I am getting an error message in my jss styling file as a couple of the methods have a 'theme' in the parenthesis for the styling to work off.
How do I go about changing this so that it renders correctly?
accessControl.component.js
import {connect, useSelector} from "react-redux";
import DataTable from "./userListTable.component";
import {Paper} from "#material-ui/core";
function AdminAccessControl(props) {
const { children, value, index, ...other } = props;
// select user object from redux
const user = useSelector(state => state.user);
// select school object from redux
const school = useSelector(state => state.diveSchool);
const classes = useStyles();
const [role, setRole] = useState({
userRole: "",
});
const handleChange = (property) => (e) => {
setRole({
// override the changed property and keep the rest
...role,
[property]: e.target.value,
});
}
return (
<div className={classes.root}>
<StyledTabs
value={value}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
aria-label="styled tabs example"
centered>
<StyledTab label="User Access Control" />
{/*<DataTable />*/}
<StyledTab label="Scuba School Access Control" />
{/*<DataTable />*/}
</StyledTabs>
<Typography className={classes.padding} />
</div>
);
}
function mapStateToProps(state){
const { user } = state.auth;
const { school } = state.diveSchool;
return {
user,
school,
};
}
export default compose(
connect(
mapStateToProps,
),
withStyles(useStyles)
)(AdminAccessControl);
myJss-style.js
export const useStyles = (theme) => ({
root: {
flexGrow: 1,
},
padding: {
padding: theme.spacing(3),
},
demo1: {
backgroundColor: theme.palette.background.paper,
},
demo2: {
backgroundColor: '#2e1534',
},
});
export const StyledTabs = () => ({
indicator: {
display: 'flex',
justifyContent: 'center',
backgroundColor: 'transparent',
'& > span': {
maxWidth: 40,
width: '100%',
backgroundColor: '#635ee7',
},
},
})((props) => <StyledTabs {...props} TabIndicatorProps={{ children: <span /> }} />);
export const StyledTab = (theme) => ({
root: {
textTransform: 'none',
color: '#fff',
fontWeight: theme.typography.fontWeightRegular,
fontSize: theme.typography.pxToRem(15),
marginRight: theme.spacing(1),
'&:focus': {
opacity: 1,
},
},
})((props) => <StyledTab disableRipple {...props} />);

How to change the progress bar background color dynamically in react material ui?

//Component Style:
const BorderLinearProgress = withStyles(theme => ({
bar: {
borderRadius: 8,
backgroundColor: "red"
}
}))(LinearProgress);
//Component use:
<BorderLinearProgress variant="determinate" value={50} />
I am new to react and material-ui.
In the above code I need to pass or change bar:backgroundColor dynamically.
Please let me know what are the options to do.
Thanks in advance
You can pass your color with the theme variable.
// Passing theme
const useStyles = makeStyles((theme) => ({
bar: props => ({
borderRadius: 8,
backgroundColor: props.color
})
}))
//Using style in component
...
const [progressColor, setProgressColor] = React.useState({ color: 'red' })
const classes = useStyles(progressColor);
// Update color based on your requirements i.e. setProgressColor({color: 'green'}) in some useEffect() when progress crosses some threshold
return (
<LinearProgress color={classes.bar} />
)
...
You can find an example in official docs: https://material-ui.com/styles/basics/#adapting-based-on-props
Below code works fine with dynamic values and colors
const LinearProgressBar: React.FC<ILinearProps> = ({ value, color }) => {
const useStyles = makeStyles({
root: {
height: 10,
borderRadius: 5
},
colorPrimary: {
backgroundColor: '#E9E9E9'
},
bar: {
borderRadius: 5,
backgroundColor: color
}
});
const classes = useStyles();
return (
<LinearProgress
variant="determinate"
value={value}
classes={{
root: classes.root,
colorPrimary: classes.colorPrimary,
bar: classes.bar
}}
/>
);
};
export default LinearProgressBar;
You can do it in two ways:
1). just Write
<LinearProgress style={{backgroundColor: "red"}} variant="determinate" value={50} />
2).
import React from 'react';
import { withStyles } from '#material-ui/core/styles';
const styles = {
LinerProgressColor: {
backgroundColor: 'red',
},
};
function BorderLinearProgress (props) {
return <LinearProgress className={LinerProgressColor} variant="determinate" value={50} />;
}
export default withStyles(styles)(BorderLinearProgress);

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

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. ✨🔥

Resources