Passing props to Material UI styles
Asked Answered
C

16

116

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!

Circinus answered 20/2, 2018 at 6:58 Comment(6)
@pritesh That will be much-unneeded code, while needed code I already showed it. Furthermore, I gave reference to the original card code at material ui website. So shall I display whole code? Remeber I am not talking about a bug, I am asking how to accomplish something. If I talk about a bug, then I have to show what code I have written. But for how to do something, it is enough I showed my tries above using code. What is your opinion now?Circinus
Since you are accessing props inside styles function so if the styles is defined inside react component then only props will be available for it.Line 15: 'props' is not defined no-undef .You might be getting this error because of it .So it matters where you wrote your style and how you are accessing it.So I am not sure exactly how is your code working.It is better to display the component code only?Misapprehension
thanks for your reply. I would appreciate you if see the code material-ui-next.com/demos/cards for styles. Or at sandbox here codesandbox.io/s/ym1q925xj1Circinus
Props aren't available in to withStyles.Impudicity
thanks @Matt. but how can I achieve my purpose? I am going to include the card inside a map iterator, and many cards will be generated, I want to make them unique by sending different colors to them.Circinus
This may help: material-ui-next.com/customization/overridesImpudicity
T
25

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:

  1. Render the component with a backgroundColor prop:
// in index.js
if (rootElement) {
  render(<Demo backgroundColor="#f00" />, rootElement);
}
  1. 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.

Transplant answered 20/2, 2018 at 18:21 Comment(3)
@HugoGresse thanks! I've touched up your edit a bit, pointing people to the better answer.Transplant
Please consider changing to one of the other answers. Inline styles should be only used as last resortCrescantia
@Crescantia That is my recommendation as well. A while back, I updated the answer to direct viewers to the one posted by James Tan.Transplant
P
163

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

Pallette answered 26/6, 2018 at 6:55 Comment(7)
Could the same effect (not creating new styled component on each render) be achieved using functional components and hooks? I'm new to React hooks so just askingUmont
Yes, you can. You may use useEffect hook and pass just style props to it to ensure it will only rerender when style props are changed.Policlinic
Could you post an answer with the useEffect hook? I'm using your solution now but want to be as performant as possible and I'm still really new to hooks and not sure how.Friable
This answer not only helped me to solve this issue in passing props to these style, but also helped me out to understand better about makeStyles method. Thanks!Raja
What if I have the useStyle declared in a separated file? I have tried with const styles = (props) => makeStyles({}); but without luckSarsen
Why is it undefined sometimesAntimasque
makeStyles is now deprecatedNatalya
R
70

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>
    );
}
Rouvin answered 15/1, 2020 at 14:3 Comment(3)
This is money for props and themeCroup
If useStyles is in a separate file this doesn't seem to work. Any solution for it?Senhorita
@SrinjoySantra are you sure you added another () after your style declaration? const { div } = useStyles(props)();Rock
K
62

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

Kassandra answered 15/7, 2020 at 15:59 Comment(0)
B
57

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} />;
}

Codesandbox Demo

Baines answered 22/10, 2021 at 13:31 Comment(0)
E
38

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.

Epiclesis answered 22/2, 2019 at 19:51 Comment(8)
As I like being able to access props on the property level, in my case it would be better to access it on the styles level const useStyles = (props) => makeStyles({}). I get dynamic styles definition from the server and I don't know what CSS properties are defined there. Is it possible with @material-ui/styles?Umont
@Jagi Since makeStyles returns a function that takes props and returns classes, you could always wrap it within your own custom function that takes props and returns classes. For example:const useStyles = (props) => { /* do stuff */ return makeStyles({}); }. Does that solve your problem? In what ways do you need to change the object passed in to makeStyles based on the props coming from the server?Epiclesis
@Jagi Oops, I meant this: const useStyles = (props, options) => { /* do stuff */ return makeStyles({})(props, options); }Epiclesis
Thanks, it works! The only thing that I'm worried about is that it will recreate styles on each render even when props haven't changed. Or is makeStyles taking care of it?Umont
That's true, makeStyles creates a function and that function will be created on every render instead of being created once. However, two thoughts: 1) if the object that you are passing into makeStyles is different on every render, then there's no getting around creating new classes on every render (at least not with Material-UI's current functionality) and 2) I wouldn't worry about performance until you have metrics to show that it's a problem for users.Epiclesis
Also note that makeStyles is a stateless hook so, it won't work with class components,Policlinic
@Epiclesis what if i also want to modify the label style as well in that case how can I pass the label style using the classNameSubtractive
@SandeepGahlawat I've updated the answer to show how.Epiclesis
T
25

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:

  1. Render the component with a backgroundColor prop:
// in index.js
if (rootElement) {
  render(<Demo backgroundColor="#f00" />, rootElement);
}
  1. 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.

Transplant answered 20/2, 2018 at 18:21 Comment(3)
@HugoGresse thanks! I've touched up your edit a bit, pointing people to the better answer.Transplant
Please consider changing to one of the other answers. Inline styles should be only used as last resortCrescantia
@Crescantia That is my recommendation as well. A while back, I updated the answer to direct viewers to the one posted by James Tan.Transplant
G
9

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>
Gearldinegearshift answered 7/3, 2021 at 7:25 Comment(0)
T
9

@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)
  })
})
Thor answered 1/11, 2021 at 15:15 Comment(0)
I
7
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>;
}
Indian answered 20/11, 2019 at 5:7 Comment(0)
P
4

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);
Polyhedron answered 29/12, 2020 at 20:8 Comment(0)
H
4

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.

Housemaid answered 26/3, 2021 at 15:44 Comment(0)
B
2

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')
Biafra answered 30/10, 2020 at 14:50 Comment(1)
I ran into recursive rendering problems with this approach, and had to wrap the withStyles in a useMemoTremendous
T
2

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

Temperate answered 24/1, 2022 at 18:13 Comment(0)
A
2

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:

red square with white text

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.

Aubrey answered 1/2, 2022 at 13:11 Comment(0)
N
0

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

Neckline answered 2/9, 2022 at 7:29 Comment(0)
B
0

@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;
    }
Bartell answered 9/9, 2022 at 15:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.