Currently, I developing a component that requires both props and themes object.
At first, it works great with theme object
const styles = theme => ({
title: {
...theme.typography.headline,
textAlign: 'center',
padding: '8px 16px',
margin: 0,
color: theme.palette.common.white,
backgroundColor: theme.palette.primary[500],
},
withStyles(styles, { withTheme: true })(Component);
....
But I also need access to props in styles object.
I tried example but it not working.
{
....
display: (props) => props.display
}
I end up combine react-jss
and withTheme
to do that
import { withTheme } from 'material-ui/styles';
import injectSheet from 'react-jss';
function withStyles(styles, Component) {
return withTheme()(injectSheet(styles)(Component));
}
export default withStyles;
....
const styles = {
title: {
display: (props) => props.display,
textAlign: 'center',
padding: '8px 16px',
margin: 0,
color: ({ theme }) => theme.palette.common.white,
backgroundColor: ({ theme }) => theme.palette.primary[500],
},
It works but I really miss
title: {
...theme.typography.headline,
We should probably be able to address the issue by making Material-UI using the same context key as react-jss: https://github.com/cssinjs/theming/blob/master/src/channel.js#L1.
Also, have a look at #7633
I have a PR ready with a react-jss interoperability example. I will add that into the docs. cc @kof
@oliviertassinari does the resolution of this mean that it should be possible now to get access to the props within the styles definition? It's not clear to me how...
@pelotom no, withStyles doesn't have access to the properties. But given how much people are asking for this feature. It's something I can prioritize, after the bug fixes. You can use the injectSheet HOC, but it's open the door to multiple issues: memory leak, hot reloading broken, no classes
composition, no internal ref access, broken theme nesting handling. At least, it's some of the issues I have been facing in the past and motivated my rewrite. I think that step by step those issues will be addressed.
@oliviertassinari It's very reassuring to hear that you'll consider prioritizing this! This would make it much easier to customize components. For example, I'd like to have a checkbox with a configurable size (i.e. width & height in pixels):
<CustomCheckbox size={16} />
If we could access props
in styles
, this would be very simple:
const styles = {
root: {
width: props => props.size,
height: props => props.size
}
}
or
const styles = props => ({
root: {
width: props.size,
height: props.size
}
})
and then:
const CustomCheckbox = ({size, classes}) => <Checkbox className={classes.root} />;
export default withStyles(styles)(CustomCheckbox);
For now, do you have any recommendations about how we should approach these types of use cases? Or do you have any estimates of when you might be able to add support for accessing props when using withStyles?
@nmchaves You use case seems to fall perfectly for the inline-style approach, you can find a bit about it in the documentation. FAQ
https://github.com/callemall/material-ui/blob/75a30061e76eae93c711ec202a2c7e4238a4f19a/docs/src/pages/style/SvgIcons.js#L38-L44
Thanks @oliviertassinari ! I was hoping I could accomplish this using withStyles
, but inline-styles will work great. And the fact that you're recommending it here + in the docs makes me feel very confident with this decision. Thanks again!
it would be nice to be able to pass a prop (image src) to the style for a backgroundImage
I'd wrap withStyle
const withStylesProps = styles =>
Component =>
props => {
console.log(props);
const Comp = withStyles(styles(props))(Component);
// return <div>lol</div>;
return <Comp {...props} />;
};
const styles = props => ({
foo: {
height: `${props.y || 50}px`,
}
});
export default withStylesProps(styles)(
props => (
<div className={props.classes.foo} style={{ ...props.style, background: 'yellow' }}>
<h1>Hello!</h1>
</div>
)
);
demo: https://codesandbox.io/s/k2y01rj3w7
(I'm surprised ^ works without any ThemeProvider
and JssProvider
set up https://codesandbox.io/s/q6v7krx6, ah it initializes it)
@caub It's working, but you need to be cautious with this pattern. The injected CSS will grow with the number of instances of the component. It's a duplicate of #7633. I haven't dug into the topic. But I believe @kof version use some performance optimization.
@caub Thanks for sharing!
@oliviertassinari there's this https://github.com/cssinjs/react-jss/blob/master/readme.md#dynamic-values in react-jss, I wonder why it couldn't be used in material-ui? Also I understand your point where you say the inline style
prop is perfect for dynamic values, but it's nicer to have all styles definitions in the same places. There's also https://github.com/airbnb/react-with-styles that would handle className
and style
for more efficient dynamic styles
I am facing the same issue can some one help me out
`import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import Drawer from 'material-ui/Drawer';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import List from 'material-ui/List';
import Typography from 'material-ui/Typography';
import IconButton from 'material-ui/IconButton';
import Hidden from 'material-ui/Hidden';
import Divider from 'material-ui/Divider';
import MenuIcon from 'material-ui-icons/Menu';
import { mailFolderListItems, otherMailFolderListItems } from './tileData';
const drawerWidth = 240;
const styles = theme => ({
root: {
width: '100%',
height: 430,
marginTop: theme.spacing.unit * 3,
zIndex: 1,
overflow: 'hidden',
},
appFrame: {
position: 'relative',
display: 'flex',
width: '100%',
height: '100%',
},
appBar: {
position: 'absolute',
marginLeft: drawerWidth,
[theme.breakpoints.up('md')]: {
width: calc(100% - ${drawerWidth}px)
,
},
},
navIconHide: {
[theme.breakpoints.up('md')]: {
display: 'none',
},
},
drawerHeader: theme.mixins.toolbar,
drawerPaper: {
width: 250,
[theme.breakpoints.up('md')]: {
width: drawerWidth,
position: 'relative',
height: '100%',
},
},
content: {
backgroundColor: theme.palette.background.default,
width: '100%',
padding: theme.spacing.unit * 3,
height: 'calc(100% - 56px)',
marginTop: 56,
[theme.breakpoints.up('sm')]: {
height: 'calc(100% - 64px)',
marginTop: 64,
},
},
});
export class ResponsiveDrawer extends React.Component {
state = {
mobileOpen: false,
};
handleDrawerToggle = () => {
this.setState({ mobileOpen: !this.state.mobileOpen });
};
render() {
const { classes, theme } = this.props;
const drawer = (
<div>
<div className={classes.drawerHeader} />
<Divider />
<List>{mailFolderListItems}</List>
<Divider />
<List>{otherMailFolderListItems}</List>
</div>
);
return (
<div className={classes.root}>
<div className={classes.appFrame}>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={this.handleDrawerToggle}
className={classes.navIconHide}
>
<MenuIcon />
</IconButton>
<Typography variant="title" color="inherit" noWrap>
Responsive drawer
</Typography>
</Toolbar>
</AppBar>
<Hidden mdUp>
<Drawer
variant="temporary"
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
open={this.state.mobileOpen}
classes={{
paper: classes.drawerPaper,
}}
onClose={this.handleDrawerToggle}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
>
{drawer}
</Drawer>
</Hidden>
<Hidden smDown implementation="css">
<Drawer
variant="permanent"
open
classes={{
paper: classes.drawerPaper,
}}
>
{drawer}
</Drawer>
</Hidden>
<main className={classes.content}>
<Typography noWrap>{'You think water moves fast? You should see ice.'}</Typography>
</main>
</div>
</div>
);
}
}
ResponsiveDrawer.propTypes = {
classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
};
export default withStyles(styles)(ResponsiveDrawer);
`
The injected CSS will grow with the number of instances of the component.
@oliviertassinari injected CSS will grow +- same way html would grow with inline styles. Static styles are rendered in separate sheets and reused across all component instances.
I did like this, though with stateless component it will re-render the withStyle
from render
to render
, we can avoid by using full pure component.
import React from 'react';
import {
withStyles,
Grid,
CircularProgress
} from 'material-ui';
const PreloadComponent = props => {
const { classes,size } = props;
return (
<Grid className={classes.container} container justify={'center'} alignItems={'center'}>
<CircularProgress size={size}/>
</Grid>
)
};
const StyleWithThemeProps = (props) => {
return withStyles(theme => ({
container: {
paddingTop: props.size*2 || 50,
paddingBottom: props.size*2 || 50,
}
}),{withTheme: true})(PreloadComponent)
};
const Preload = props => {
const { size } = props;
const WithStylesPreloadComponent = StyleWithThemeProps(props);
return (
<WithStylesPreloadComponent {...props}/>
)
};
Preload.defaultProps = {
size: 20
};
export default Preload;
We can use full pure component to avoid updating
const PreloadComponent = props => {
const { classes,size } = props;
return (
<Grid className={classes.container} container justify={'center'} alignItems={'center'}>
<CircularProgress size={size}/>
</Grid>
)
};
const StyleWithThemeProps = (props) => {
return withStyles(theme => ({
container: {
paddingTop: props.size*2 || 50,
paddingBottom: props.size*2 || 50,
}
}),{withTheme: true})(PreloadComponent)
};
class PreloadFull extends React.PureComponent {
constructor(props,context) {
super(props);
}
componentWillMount() {
this.StyledPreloadFull = StyleWithThemeProps(this.props);
}
componentWillUpdate(nextProps) {
this.StyledPreloadFull = StyleWithThemeProps(nextProps);
}
render() {
const { StyledPreloadFull,props } = this;
return (
<StyledPreloadFull {...props}/>
);
}
}
PreloadFull.defaultProps = {
size: 20
};
export default PreloadFull;
@up209d It works, but it's quite painful, I'll try to modify withStyles
, to use more directly https://github.com/cssinjs/react-jss which can pass props in values
@SrikanthChebrolu could you move your message to a different issue, since it's not in-topic?
Just curious what the status is on this? I've been reading through this issue, the JSS docs, material-ui docs, and yet to find a solution for Mui+Jss+TypeScript that doesn't require me to use inline styles. Putting a few inline styles is sometimes unavoidable, but in my case there are multiple styles that have many different states, all relying on theme and props together :disappointed:
@chazsolo Hey Chaz, you actually can use injectSheet
from react-jss
instead of withStyles
from mui
. By that way you can have both props
and theme
.
import injectSheet from 'react-jss';
const styles = theme => ({
container: {
color: props => theme.palette[props.color || 'primary'].main
}
});
...
export default injectSheet(styles)(AnyComponent);
import { JssProvider, jss, createGenerateClassName } from 'react-jss/lib';
import { MuiThemeProvider } from 'material-ui';
const generateClassName = createGenerateClassName();
...
<JssProvider jss={jss} generateClassName={generateClassName}>
<MuiThemeProvider theme={props.theme} sheetsManager={new Map()}>
<App/>
</MuiThemeProvider>
</JssProvider>
@chazsolo I think you want to follow this issue https://github.com/cssinjs/jss/issues/682
Thanks @kof and @up209d - subscribed and giving up209d's example a shot.
@up209d
Unfortunately I don't think that's gonna work for me - I've implemented what you suggested, and I can see the props within the function call inside the styles
object, but I continue to get errors. IAm I just missing types? I'm extending WithStyles
in props Interfaces so I have access to the classes
object in props (now I'm wondering if that's the problem referenced in https://github.com/mui-org/material-ui/issues/8726#issuecomment-337482040)
TS2344: Type '(theme: ITheme) => { arc: { stroke: string; strokeWidth: (props: any) => string | number; }; arcM...' does not satisfy the constraint 'string | Record<string, CSSProperties> | StyleRulesCallback<string>'.
Type '(theme: ITheme) => { arc: { stroke: string; strokeWidth: (props: any) => string | number; }; arcM...' is not assignable to type 'StyleRulesCallback<string>'.
Type '{ arc: { stroke: string; strokeWidth: (props: any) => string | number; }; arcMovement: { strokeDa...' is not assignable to type 'Record<string, CSSProperties>'.
Property 'arc' is incompatible with index signature.
Type '{ stroke: string; strokeWidth: (props: any) => string | number; }' is not assignable to type 'CSSProperties'.
Types of property 'strokeWidth' are incompatible.
Type '(props: any) => string | number' is not assignable to type 'string | number | undefined'.
Type '(props: any) => string | number' is not assignable to type 'number'.
My theme looks like:
import { ITheme } from '...';
export default (theme: ITheme) => ({
arc: {
// ...
strokeWidth: (props: any): number | string => {
// this logs the correct data I'm expecting
console.log(props.data[0].properties.name)
return 1.5
}
},
arcMovement: {
// ...
},
})
The interesting this is, when I use the classes
object within my component, arc
and arcMovement
are valid properties:
// from Chrome console
{
arc: "Arcs-arc-0-2-1 Arcs-arc-0-2-3",
arcMovement: "Arcs-arcMovement-0-2-2"
}
Update
I was able to get this working, but as noted in the comment above, I had to strip out all references to WithStyles
, withStyles
, and I lose classes
composition and theme nesting. I'm gonna give it a rest now and just keep an eye on the threads. Thanks for all the help!
@chazsolo Hey Chaz, I am not sure but is that you want to access to classes
inside the props
of the style
object. If so, I think it is impossible since the classes
only available after jss
processed the style
object, how can you access classes
before a process of making classes
even hasn't been triggered?
I think @caub already provided a solution. Just repost the solution with little twist. No need any extra library.
Build your own wrapper withStylesProps
.
import { withStyles } from 'material-ui/styles';
const styles = ( theme, props ) => ({
exampleStyle: {
color: 'red' // <-- or try theme.palette.primary[600]
}
})
const withStylesProps = ( styles ) =>
Component =>
props => {
const Comp = withStyles(theme => styles(theme, props))(Component);
return <Comp {...props} />;
};
const YourComponent = ({ classes }) =>
<Typography type="display4" className={classes.exampleStyle}>{type}</Typography>
export default withStylesProps(styles)(YourComponent);
If you don't like create withStylesProps
for every component, try adding it in separated file and import wherever you want.
@iamthuypham Thanks for the tip. However when i wrap my component with withStylesProps
, the animation of transition component <Collapse
i am using somewhere inside the wrapped component stops working.
@jdolinski1 Can you copy/paste your code example?
@iamthuypham your solution has the drawback of creating a new <style>
tag each time a component gets created. Also, you might be careful when using defaultProps and add them to your HOC'd component and not the base components.
All of that is supported by react-jss
, can't it be supported natively by material-ui
?
Also, I think @jdolinski1 's problem is that your code does not propagate children
the wrapped component may have.
@iamthuypham I think it is not recommended to do that, as I used to do like that in the past, and you might experience the poor performance as long as the app growth very soon. Creating a new instance of component
with new jss style
object is not good in term of coding principle because the style
object gotta be re-rendered entirely, again and again, every time per props
change. Using injectSheet
from react-jss
is better choice. If you look into the injectSheet
you will see that it break your style
object into 2 pieces (static
& dynamic
) so only the dynamic
get re-rendered when props
change.
how to use plugins such as jss-nested with injectSheet?.
with injectSheet i can't get '&:hover' statements work.
with withStyles i can't access to props...
@koutsenko Here is an example:
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
},
border: 0,
borderRadius: 3,
color: "white",
height: 48,
padding: "0 30px"
}
});
export default function Hook() {
const classes = useStyles({
color: "red",
hover: "blue"
});
return <Button className={classes.root}>Hook</Button>;
}
https://codesandbox.io/s/pw32vw2j3m
I hope it helps.
Wow, it's amazing the progress we have made in ~1 year 馃槏.
now how do you typescript that?
@stunaz Good question. I don't know. I haven't looked into it. @eps1lon has done the TypeScript definition of the module. You can use it as a starting point.
https://github.com/mui-org/material-ui/blob/f4281a77d15b0d6eec9d33cdc358cfb89844996d/packages/material-ui-styles/src/index.d.ts#L72
@koutsenko Here is an example:
Thanks @oliviertassinari , with "react@next" it works now.
@koutsenko If you couldn't make jss-nested
work, it must be a configuration issue somewhere in your coding. As jss-nested
is included in jss-default-preset
, so it just works oob
@oliviertassinari
Can you also set the entire styles object for a given selector with props? To where you can conditionally apply a property?
For instance, like this
withStyles({
root: {
'& > path': (props) => {
if(props.color)
return {
fill: props.color
};
return {};
}
}
})
So that, if the prop does not exist, then it uses the previous fill value, rather than something else that I have to set it to? For instance, there are other rules that would normally apply to fill, but I only want to set this new fill property if the color
prop is set.
Thanks!
@Guardiannw For some reason your variant doesn't work. Maybe @kof could raise our light on why 馃挕. You can do one of the following:
// 馃弳
const useStyles = makeStyles({
root: {
"& > span": {
backgroundColor: props => props.color || null,
}
}
});
// or
const useStyles = makeStyles({
root: props => ({
"& > span": {
backgroundColor: props.color || null
}
})
});
@oliviertassinari I am having a hard time getting your second option to work with the withStyles
function. Does it only work with makeStyles
and hooks?
@Guardiannw It's working with any of the APIs of @material-ui/styles
.
@oliviertassinari looks like a valid syntax, fn values were added in v10, so either v9 was used or I need a codesandbox reproduction
Ok, that's what I tried it with. Might have to try again.
@oliviertassinari I have a question about the use of @materia-ui/styles, is It available and to use in a production environment?, in the documentation indicates that it doesn't work with the stable version, I'm using the "3.9.1", the example https://github.com/mui-org/material-ui/issues/8726#issuecomment-452047345 that you present it has a powerful and useful feature that I need. In these issues, I saw many comments from a different perspective and also I like the solution https://github.com/mui-org/material-ui/issues/8726#issuecomment-363546636 of @caub, but your comment about his solution is good.
@contrerasjf0 @material-ui/styles
is only available as an alpha release. We treat alpha versions like most packages in the react ecosystem. I would recommend you never use any alpha packages in production. If you do you should expect bugs and breaking changes between any release i.e. you should be able to handle the churn alpha versions add.
What I hope is that people use those versions either in hobby projects or use it on a separate branch that is not deployed to production but still tested just like the production branch. I do appreciate everyone that uses those alpha versions and gives us feedback for them.
@up209d yes, your solution work, but with
styles = { name: { cssprop: props => {} }
notation, not
styles = props => ({ name: { cssprop: {} })
Also, JssProvider isn't necessary.
@koutsenko
// at value level:
styles = { name: { cssprop: props => value }
styles = theme => ({ name: { cssprop: props => value })
// at class name level
styles = { name: props => ({ cssprop: value }) }
styles = theme => ({ name: props => ({ cssprop: value }) })
You can't access props
at the top level, even as a second argument after theme
I found a way
// MyComponent.tsx
import React, { PureComponent } from 'react';
import { myComponentWithStyles } from './myComponentWithStyles';
export interface MyComponentProps {
copy: string;
size?: number;
}
export class Twemoji extends PureComponent<myComponentWithStyles> {
public render() {
const { copy, classes } = this.props;
return (
<div className={classes.message}>
{copy}
<img src="https://via.placeholder.com/150" />
</div>
);
}
}
// myComponentWithStyles.tsx
import React from 'react';
import { withStyles, WithStyles, Theme } from '@material-ui/core';
import { MyComponent, MyComponentProps } from './my-component';
const styles = (props: Theme & MyComponentProps) => ({
message: {
fontSize: props.typography.caption.fontSize,
'box-sizing': 'content-box',
'& img': {
width: `${props.size || 24}px`,
height: `${props.size || 24}px`,
padding: '0 4px',
verticalAlign: 'middle',
},
},
});
export type myComponentWithStyles = WithStyles<any>;
export const Component = (props: MyComponentProps) => {
const StyledComponent = withStyles((theme: Theme) => styles({ ...props, ...theme }))(
MyComponent
);
return <StyledComponent {...props} />;
};
md5-d0e1b51e375682cf2aad9c4d66b6c73a
<Component size={12} />
@andreasonny83 Avoid this pattern. We are providing a native API in v4.
@oliviertassinari thanks for the update. Is that pattern already available? Any documentation available?
https://next.material-ui.com/css-in-js/basics/#adapting-based-on-props
One last question @oliviertassinari . Can I use makeStyles
in combination with withStyles
?
I cannot find documentation for that. What I'm trying to do is this:
const useStyles = makeStyles({
message: {
boxSizing: 'content-box'
}
});
export const ComponentWithStyles = withStyles(useStyles())(MyComponent);
@andreasonny83
makeStyles
is for react-hookswithStyles
HOC accepts the same syntaxUse either one or the other, in your example just remove makeStyles
:
const styles = { message: {boxSizing: 'content-box', background: props => props.bg} };
export const ComponentWithStyles = withStyles(styles)(MyComponent);
Gday folks thought id share my current solution with reference to the above discussion, hopefully it helps someone or someone could offer better advice on my current solution. For my signin page id like a random background image but id still like to maintain the power of the material ui api. The AuthPage is just the parent presentation layer that takes the individual auth components (signin, locked, forgotten-password, password-reset, etc) as children. Can confirm with each page refresh a new background loads aswell as a nice strongly typed props within AuthPageContainer prop
// AuthPage.styles.tsx
import { Container } from "@material-ui/core";
import { ContainerProps } from "@material-ui/core/Container";
import { withStyles } from "@material-ui/core/styles";
import React from "react";
interface IAuthContainerProps extends ContainerProps {
background: string;
}
export const AuthContainer = withStyles({
root: props => ({
alignItems: "center",
backgroundImage: `url(${props.background})`,
backgroundPosition: "50% 50%",
backgroundRepeat: "no-repeat",
backgroundSize: "cover",
display: "flex",
height: "100vh",
justifyContent: "center",
margin: 0,
padding: 0,
width: "100%"
})
})((props: IAuthContainerProps) => <Container maxWidth={false} {...props} />);
// AuthPage.tsx
import React from "react";
import forest from "../../assets/backgrounds/forest.jpg";
import sky from "../../assets/backgrounds/sky.jpg";
import uluru from "../../assets/backgrounds/uluru.jpg";
import { AuthContainer } from "./AuthPage.styles";
const AuthPage = ({ children }) => {
const generateBackground = () => {
const backgrounds = [forest, sky, uluru];
const index = Math.floor(Math.random() * backgrounds.length);
return backgrounds[index];
};
return (
<AuthContainer background={generateBackground()}>{children}</AuthContainer>
);
};
export default AuthPage;
simply do something like this:
// styles.js
export default theme => ({
root: props => ({
// some styles
}),
...
});
//container.js
export default withStyles(styles)(MyComponent);
what about passing also state?
@luky1984
You can't. Instead you may do:
// Component.js
<Button
className={`
${classes.button}
${this.state.isEnable
? classes.enable
: classes.disable}
`}
/>
Or use clsx https://www.npmjs.com/package/clsx instead
@caub Your solution ruins the jss generated classname order.
Like written here: https://github.com/mui-org/material-ui/issues/8726#issuecomment-363546636
I have tried to use your solution, but your HOC component (withStylesProps) solution delays the call of it's withStyles, because it's wrapped, so calling it with classNames doesn't override the css.
Like: https://codesandbox.io/s/hocs-8uhw1?file=/index.js
background should be #0000000 and color: blue
Most helpful comment
@oliviertassinari It's very reassuring to hear that you'll consider prioritizing this! This would make it much easier to customize components. For example, I'd like to have a checkbox with a configurable size (i.e. width & height in pixels):
If we could access
props
instyles
, this would be very simple:or
and then:
For now, do you have any recommendations about how we should approach these types of use cases? Or do you have any estimates of when you might be able to add support for accessing props when using withStyles?