With material-ui's makeStyles, is there an option to spread props in an object applying all the styles?
I.e.:
const useStyles = makeStyles(theme) => ({
card: {
...props # <- props has stuff like backgroundColor, fontSize etc.
},
thisWorks: {
backgroundColor: (props) => props.backgroundColor,
fontSize: (props) => props.fontSize,
}
})
Yes, this is possible. Have a look at the documentation here.
You can pass props to your useStyles hook.
Since you get the props as params of your declared functions inside your makeStyles, you have to use functions. But this does not prevent you from using the spread operator.
Just keep in mind that you need to return an object at the end
const useStyles = makeStyles((theme) => ({
// use the spread operator
card: (props) => ({
// dynamic styles from props
...props,
// static styles
border: "1px solid red"
}),
// more explicit way if you need just specific props
card2: (props) => {
const { backgroundColor, color } = props;
return {
backgroundColor,
color
};
}
}));
...
const MyComponent = () => {
const myProps = {
backgroundColor: "#000",
fontSize: "32px",
};
const classes = useStyles(myProps)
return (<div className={classes.card}>
...
</div>
)
}
Here is a live example:
I'm using react-jss in my React project.
I want to set up some of the styles dynamically based on some property sent through props in createUseStyles
For eg :
In the component
const useStyles = createUseStyles (styleProp)
And in the stylesheet
type1 = () => {
return {
className : {
backgroundColor : 'blue',
}
}
}
type2 = () => {
return {
className : {
backgroundColor : 'green',
}
}
}
switch (styleProp) {
case 'type1' :
return type1 ();
case 'type2' :
return type2 ();
}
The first time I call this with a particular styleProp it works fine. It renders the correct bg color. But when the type changes the next time it goes through the correct type function but the style rendered is incorrect.
If someone could tell me what I'm doing wrong that would be great!
From the official documentation, you need to do something similar like this:
import React from 'react'
import {createUseStyles} from 'react-jss'
const useStyles = createUseStyles({
myButton: {
padding: props => props.spacing
},
myLabel: props => ({
display: 'block',
color: props.labelColor,
fontWeight: props.fontWeight,
fontStyle: props.fontStyle
})
})
const Button = ({children, ...props}) => {
const classes = useStyles(props)
return (
<button className={classes.myButton}>
<span className={classes.myLabel}>{children}</span>
</button>
)
}
Button.defaultProps = {
spacing: 10,
fontWeight: 'bold',
labelColor: 'red'
}
const App = () => <Button fontStyle="italic">Submit</Button>
Now if you want to update it dynamically, you can maintain a state, and store the variable value in that state, and you can update the state using the setState function, this will surely update your UI with the correct expected output.
I am making a searchable Dropdown and getting following eslint warning:
React Hook useEffect has missing dependencies: 'filterDropDown' and 'restoreDropDown'. Either include them or remove the dependency array.
import React, { useState, useEffect, useCallback } from "react";
const SearchableDropDown2 = () => {
const [searchText, setSearchText] = useState("");
const [dropdownOptions, setDropdownOptions] = useState([
"React",
"Angular",
"Vue",
"jQuery",
"Nextjs",
]);
const [copyOfdropdownOptions, setCopyOfDropdownOptions] = useState([
...dropdownOptions,
]);
const [isExpand, setIsExpand] = useState(false);
useEffect(() => {
searchText.length > 0 ? filterDropDown() : restoreDropDown();
}, [searchText]);
const onClickHandler = (e) => {
setSearchText(e.target.dataset.myoptions);
setIsExpand(false);
};
const onSearchDropDown = () => setIsExpand(true);
const closeDropDownHandler = () => setIsExpand(false);
const filterDropDown = useCallback(() => {
const filteredDropdown = dropdownOptions.filter((_) =>
_.toLowerCase().includes(searchText.toLowerCase())
);
setDropdownOptions([...filteredDropdown]);
}, [dropdownOptions]);
const restoreDropDown = () => {
if (dropdownOptions.length !== copyOfdropdownOptions.length) {
setDropdownOptions([...copyOfdropdownOptions]);
}
};
const onSearchHandler = (e) => setSearchText(e.target.value.trim());
return (
<div style={styles.mainContainer}>
<input
type="search"
value={searchText}
onClick={onSearchDropDown}
onChange={onSearchHandler}
style={styles.search}
placeholder="search"
/>
<button disabled={!isExpand} onClick={closeDropDownHandler}>
-
</button>
<div
style={
isExpand
? styles.dropdownContainer
: {
...styles.dropdownContainer,
height: "0vh",
}
}
>
{dropdownOptions.map((_, idx) => (
<span
onClick={onClickHandler}
style={styles.dropdownOptions}
data-myoptions={_}
value={_}
>
{_}
</span>
))}
</div>
</div>
);
};
const styles = {
mainContainer: {
padding: "1vh 1vw",
width: "28vw",
margin: "auto auto",
},
dropdownContainer: {
width: "25vw",
background: "grey",
height: "10vh",
overflow: "scroll",
},
dropdownOptions: {
display: "block",
height: "2vh",
color: "white",
padding: "0.2vh 0.5vw",
cursor: "pointer",
},
search: {
width: "25vw",
},
};
I tried wrapping the filterDropDown in useCallback but then the searchable dropdown stopped working.
Following are the changes incorporating useCallback:
const filterDropDown = useCallback(() => {
const filteredDropdown = dropdownOptions.filter((_) =>
_.toLowerCase().includes(searchText.toLowerCase())
);
setDropdownOptions([...filteredDropdown]);
}, [dropdownOptions, searchText]);
My suggestion would be to not use useEffect at all in the context of what you are achieving.
import React, {useState} from 'react';
const SearchableDropDown = () => {
const [searchText, setSearchText] = useState('');
const dropdownOptions = ['React','Angular','Vue','jQuery','Nextjs'];
const [isExpand, setIsExpand] = useState(false);
const onClickHandler = e => {
setSearchText(e.target.dataset.myoptions);
setIsExpand(false);
}
const onSearchDropDown = () => setIsExpand(true);
const closeDropDownHandler = () => setIsExpand(false);
const onSearchHandler = e => setSearchText(e.target.value.trim());
return (
<div style={styles.mainContainer}>
<input
type="search"
value={searchText}
onClick={onSearchDropDown}
onChange={onSearchHandler}
style={styles.search}
placeholder="search"
/>
<button disabled={!isExpand} onClick={closeDropDownHandler}>
-
</button>
<div
style={
isExpand
? styles.dropdownContainer
: {
...styles.dropdownContainer,
height: '0vh',
}
}
>
{dropdownOptions
.filter(opt => opt.toLowerCase().includes(searchText.toLowerCase()))
.map((option, idx) =>
<span key={idx} onClick={onClickHandler} style={styles.dropdownOptions} data-myoptions={option} value={option}>
{option}
</span>
)}
</div>
</div>
);
};
const styles = {
mainContainer: {
padding: '1vh 1vw',
width: '28vw',
margin: 'auto auto'
},
dropdownContainer: {
width: '25vw',
background: 'grey',
height: '10vh',
overflow: 'scroll'
},
dropdownOptions: {
display: 'block',
height: '2vh',
color: 'white',
padding: '0.2vh 0.5vw',
cursor: 'pointer',
},
search: {
width: '25vw',
},
};
Just filter the dropDownOptions before mapping them to the span elements.
also, if the list of dropDownOptions is ever going to change then use setState else just leave it as a simple list. If dropDownOptions comes from an api call, then use setState within the useEffect hook.
Your current code:
// 1. Filter
const filterDropDown = useCallback(() => {
const filteredDropdown = dropdownOptions.filter((_) =>
_.toLowerCase().includes(searchText.toLowerCase())
);
setDropdownOptions([...filteredDropdown]);
}, [dropdownOptions]);
// 2. Restore
const restoreDropDown = () => {
if (dropdownOptions.length !== copyOfdropdownOptions.length) {
setDropdownOptions([...copyOfdropdownOptions]);
}
};
// 3. searchText change effect
useEffect(() => {
searchText.length > 0 ? filterDropDown() : restoreDropDown();
}, [searchText]);
Above code produces eslint warning that:
React Hook useEffect has missing dependencies: 'filterDropDown' and 'restoreDropDown'. Either include them or remove the dependency array
BUT after doing what the warning says, our code finally looks like:
// 1. Filter
const filterDropDown = useCallback(() => {
const filteredDropdown = dropdownOptions.filter((_) =>
_.toLowerCase().includes(searchText.toLowerCase())
);
setDropdownOptions(filteredDropdown);
}, [dropdownOptions, searchText]);
// 2. Restore
const restoreDropDown = useCallback(() => {
if (dropdownOptions.length !== copyOfdropdownOptions.length) {
setDropdownOptions([...copyOfdropdownOptions]);
}
}, [copyOfdropdownOptions, dropdownOptions.length]);
// 3. searchText change effect
useEffect(() => {
searchText.length > 0 ? filterDropDown() : restoreDropDown();
}, [filterDropDown, restoreDropDown, searchText]);
But the above code has formed an infinite loop because:
"Filter" function is OK. (It will be recreated when searchText or dropdownOptions change. And that makes sense.)
"Restore" function is OK. (It will be recreated when copyOfdropdownOptions or dropdownOptions.length change. And this too makes sense.)
searchText change effect looks bad. This effect will run whenever:
=> (a). searchText is changed (OK), or
=> (b). "Filter" function is changed (Not OK because this function itself will change when this hook is run. Point 1), or
=> (c). "Restore" function is changed (Not OK because this function itself will change when this hook is run. Point 2)
We can clearly see an infinite loop in Point 3 (a, b, c).
How to fix it?
There can be few ways. One could be:
The below copy is constant, so either keep it in a Ref or move it outside the component definition:
const copyOfdropdownOptions = useRef([...dropdownOptions]);
And, move the "Filter" and "Restore" functions inside the hook (i.e. no need to define it outside and put it as a dependency), like so:
useEffect(() => {
if (searchText.length) {
// 1. Filter
setDropdownOptions((prev) =>
prev.filter((_) => _.toLowerCase().includes(searchText.toLowerCase()))
);
} else {
// 2. Restore
setDropdownOptions([...copyOfdropdownOptions.current]);
}
}, [searchText]);
As you can see the above effect will run only when searchText is changed.
Given the Card code as in here. How can I update the card style or any material UI style as from:
const styles = theme => ({
card: {
minWidth: 275,
},
To such follows:
const styles = theme => ({
card: {
minWidth: 275, backgroundColor: props.color
},
when I tried the latest one, I got
Line 15: 'props' is not defined no-undef
when I updated code to be :
const styles = theme => (props) => ({
card: {
minWidth: 275, backgroundColor: props.color
},
also
const styles = (theme ,props) => ({
card: {
minWidth: 275, backgroundColor: props.color
},
Instead of
const styles = theme => ({
card: {
minWidth: 275, backgroundColor: props.color
},
I got the component card style at the web page messy.
By the way, I pass props as follows:
<SimpleCard backgroundColor="#f5f2ff" />
please help!
Deleted the old answer, because it's no reason for existence.
Here's what you want:
import React from 'react';
import { makeStyles } from '#material-ui/core';
const useStyles = makeStyles({
firstStyle: {
backgroundColor: props => props.backgroundColor,
},
secondStyle: {
color: props => props.color,
},
});
const MyComponent = ({children, ...props}) =>{
const { firstStyle, secondStyle } = useStyles(props);
return(
<div className={`${firstStyle} ${secondStyle}`}>
{children}
</div>
)
}
export default MyComponent;
Now you can use it like:
<MyComponent color="yellow" backgroundColor="purple">
Well done
</MyComponent>
Official Documentation
Solution for how to use both props and theme in material ui :
const useStyles = props => makeStyles( theme => ({
div: {
width: theme.spacing(props.units || 0)
}
}));
export default function ComponentExample({ children, ...props }){
const { div } = useStyles(props)();
return (
<div className={div}>{children}</div>
);
}
Here the Typescript solution:
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import {Theme} from '#material-ui/core';
export interface StyleProps {
height: number;
}
const useStyles = makeStyles<Theme, StyleProps>(theme => ({
root: {
background: 'green',
height: ({height}) => height,
},
}));
export default function Hook() {
const props = {
height: 48
}
const classes = useStyles(props);
return <Button className={classes.root}>Styled with Hook API</Button>;
}
If you want to play with it, try it in this CodeSandbox
In MUI v5, this is how you access the props when creating the style object using styled():
import { styled } from "#mui/material";
const StyledBox = styled(Box)(({ theme, myColor }) => ({
backgroundColor: myColor,
width: 30,
height: 30
}));
For folks who use typescript, you also need to add the prop type to the CreateStyledComponent:
type DivProps = {
myColor: string;
};
const Div = styled(Box)<DivProps>(({ theme, myColor }) => ({
backgroundColor: myColor,
width: 30,
height: 30
}));
<StyledBox myColor="pink" />
If you want to use system props in your custom component like Box and Typography, you can use extendSxProp like the example below:
import { unstable_extendSxProp as extendSxProp } from "#mui/system";
const StyledDiv = styled("div")({});
function DivWithSystemProps(inProps) {
const { sx } = extendSxProp(inProps);
return <StyledDiv sx={sx} />;
}
<DivWithSystemProps
bgcolor="green"
width={30}
height={30}
border="solid 1px red"
/>
Explanation
styled("div")(): Add the sx props to your custom component
extendSxProp(props): Gather the top level system props and put it inside the sx property:
const props = { notSystemProps: true, color: 'green', bgcolor: 'red' };
const finalProps = extendSxProp(props);
// finalProps = {
// notSystemProps: true,
// sx: { color: 'green', bgcolor: 'red' }
// }
To use with typescript, you need to add the type for all system properties:
type DivSystemProps = SystemProps<Theme> & {
sx?: SxProps<Theme>;
};
function DivWithSystemProps(inProps: DivSystemProps) {
const { sx, ...other } = extendSxProp(inProps);
return <StyledDiv sx={sx} {...other} />;
}
Here's the official Material-UI demo.
And here's a very simple example. It uses syntax similar to Styled Components:
import React from "react";
import { makeStyles, Button } from "#material-ui/core";
const useStyles = makeStyles({
root: {
background: props => props.color,
"&:hover": {
background: props => props.hover
}
},
label: { fontFamily: props => props.font }
});
export function MyButton(props) {
const classes = useStyles(props);
return <Button className={classes.root} classes={{ label: classes.label }}>My Button</Button>;
}
// and the JSX...
<MyButton color="red" hover="blue" font="Comic Sans MS" />
This demo uses makeStyles, but this feature is also available in styled and withStyles.
This was first introduced in #material-ui/styles on Nov 3, 2018 and was included in #material-ui/core starting with version 4.
This answer was written prior to version 4.0 severely out of date!
Seriously, if you're styling a function component, use makeStyles.
The answer from James Tan is the best answer for version 4.x
Anything below here is ancient:
When you're using withStyles, you have access to the theme, but not props.
Please note that there is an open issue on Github requesting this feature and some of the comments may point you to an alternative solution that may interest you.
One way to change the background color of a card using props would be to set this property using inline styles. I've forked your original codesandbox with a few changes, you can view the modified version to see this in action.
Here's what I did:
Render the component with a backgroundColor prop:
// in index.js
if (rootElement) {
render(<Demo backgroundColor="#f00" />, rootElement);
}
Use this prop to apply an inline style to the card:
function SimpleCard(props) {
// in demo.js
const { classes, backgroundColor } = props;
const bull = <span className={classes.bullet}>•</span>;
return (
<div>
<Card className={classes.card} style={{ backgroundColor }}>
<CardContent>
// etc
Now the rendered Card component has a red (#F00) background
Take a look at the Overrides section of the documentation for other options.
#mui v5
You can use styled() utility (Make sure that you're importing the correct one) and shouldForwardProp option.
In the following example SomeProps passed to a div component
import { styled } from '#mui/material'
interface SomeProps {
backgroundColor: 'red'|'blue',
width: number
}
const CustomDiv = styled('div', { shouldForwardProp: (prop) => prop !== 'someProps' })<{
someProps: SomeProps;
}>(({ theme, someProps }) => {
return ({
backgroundColor: someProps.backgroundColor,
width: `${someProps.width}em`,
margin:theme.spacing(1)
})
})
import React from "react";
import { makeStyles } from "#material-ui/styles";
import Button from "#material-ui/core/Button";
const useStyles = makeStyles({
root: {
background: props => props.color,
"&:hover": {
background: props => props.hover
}
}
});
export function MyButton(props) {
const classes = useStyles({color: 'red', hover: 'green'});
return <Button className={classes.root}>My Button</Button>;
}
Was missing from this thread a props use within withStyles (and lead to think it wasn't supported)
But this worked for me (say for styling a MenuItem):
const StyledMenuItem = withStyles((theme) => ({
root: {
'&:focus': {
backgroundColor: props => props.focusBackground,
'& .MuiListItemIcon-root, & .MuiListItemText-primary': {
color: props => props.focusColor,
},
},
},
}))(MenuItem);
And then use it as so:
<StyledMenuItem focusColor={'red'} focusBackground={'green'}... >...</StyledMenuItem>
I spent a couple of hours trying to get withStyles to work with passing properties in Typescript. None of the solutions I found online worked with what I was trying to do, so I ended up knitting my own solution together, with snippets from here and there.
This should work if you have external components from, lets say Material UI, that you want to give a default style, but you also want to reuse it by passing different styling options to the component:
import * as React from 'react';
import { Theme, createStyles, makeStyles } from '#material-ui/core/styles';
import { TableCell, TableCellProps } from '#material-ui/core';
type Props = {
backgroundColor?: string
}
const useStyles = makeStyles<Theme, Props>(theme =>
createStyles({
head: {
backgroundColor: ({ backgroundColor }) => backgroundColor || theme.palette.common.black,
color: theme.palette.common.white,
fontSize: 13
},
body: {
fontSize: 12,
},
})
);
export function StyledTableCell(props: Props & Omit<TableCellProps, keyof Props>) {
const classes = useStyles(props);
return <TableCell classes={classes} {...props} />;
}
It may not be the perfect solution, but it seems to work. It's a real bugger that they haven't just amended withStyles to accept properties. It would make things a lot easier.
Solution for TypeScript with Class Component:
type PropsBeforeStyle = {
propA: string;
propB: number;
}
const styles = (theme: Theme) => createStyles({
root: {
color: (props: PropsBeforeStyle) => {}
}
});
type Props = PropsBeforeStyle & WithStyles<typeof styles>;
class MyClassComponent extends Component<Props> {...}
export default withStyles(styles)(MyClassComponent);
export const renderButton = (tag, method, color) => {
const OkButton = withStyles({
root: {
"color": `${color}`,
"filter": "opacity(0.5)",
"textShadow": "0 0 3px #24fda39a",
"backgroundColor": "none",
"borderRadius": "2px solid #24fda3c9",
"outline": "none",
"border": "2px solid #24fda3c9",
"&:hover": {
color: "#24fda3c9",
border: "2px solid #24fda3c9",
filter: "opacity(1)",
},
"&:active": {
outline: "none",
},
"&:focus": {
outline: "none",
},
},
})(Button);
return (
<OkButton tag={tag} color={color} fullWidth onClick={method}>
{tag}
</OkButton>
);
};
renderButton('Submit', toggleAlert, 'red')
Here's another way of dynamically passing props to hook's API in MUI v5
import React from "react";
import { makeStyles } from "#mui/styles";
import { Theme } from "#mui/material";
interface StyleProps {
height: number;
backgroundColor: string;
}
const useStyles = makeStyles<Theme>((theme) => ({
root: ({ height, backgroundColor }: StyleProps) => ({
background: backgroundColor,
height: height
})
}));
export default function Hook() {
const props = {
height: 58,
backgroundColor: "red"
};
const classes = useStyles(props);
return (
<button className={classes.root}>
another way of passing props to useStyle hooks
</button>
);
}
here's the codesandbox https://codesandbox.io/s/styles-with-props-forked-gx3bf?file=/demo.tsx:0-607
Here's 2 full working examples of how to pass props to MUI v5 styles. Either using css or javascript object syntax.
With css syntax:
import { styled } from '#mui/system'
interface Props {
myColor: string
}
const MyComponent = ({ myColor }: Props) => {
const MyStyledComponent = styled('div')`
background-color: ${myColor};
.my-paragraph {
color: white;
}
`
return (
<MyStyledComponent >
<p className="my-paragraph">Hello there</p>
</MyStyledComponent >
)
}
export default MyComponent
Note that we define MyStyledComponent within MyComponent, making the scoped props available to use in the template string of the styled() function.
Same thing with javascript object syntax:
import { styled } from '#mui/system'
const MyComponent = ({ className }: any) => {
return (
<div className={className}>
<p className="my-paragraph">Hello there</p>
</div>
)
}
interface Props {
myColor: string
}
const MyStyledComponent = styled(MyComponent)((props: Props) => ({
backgroundColor: props.myColor,
'.my-paragraph': { color: 'white' },
}))
export default MyStyledComponent
For this second example, please note how we pass the className to the component we want to apply the styles to. The styled() function will pass a className prop with the styles you define. You typically want to apply that to your root element. In this case the div.
Result:
I'm sure there's other variations of how to do this, but these two are easy to implement and understand.
You might need to memoize the calculated styles, and maybe not use this approach if your props changes a lot. I don't think it's very performant.
Using styled-components, sometimes you only want to apply styles if the prop is passed, in such cases you can do something like this (remove : {foo: boolean} if not using TypeScript):
const SAnchor = styled("a", {
shouldForwardProp: prop => prop !== "foo",
})(({foo = false}: {foo: boolean}) => ({
...(foo && {
color: "inherit",
textDecoration: "none",
}),
}));
Object spread source
shouldForwardProp docs
#mui v5
I use theme and prop from JSON object
const Answerdiv = styled((props) => {
const { item_style, ...other } = props;
return <div {...other}></div>;
})(({ theme, item_style }) => {
for(var i of Object.keys(item_style)){
item_style[i] =Object.byString(theme,item_style[i])|| item_style[i];
}
return (item_style)
});
component use
<Answerdiv item_style={(item_style ? JSON.parse(item_style) : {})}>
for Object.byString
Object.byString = function(o, s) {
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
s = s.replace(/^\./, ''); // strip a leading dot
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
}