-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Discussion: are callbacks to withStyles always the best way to obtain the theme? #9272
Comments
Building an app here. So no need for |
I don't agree, mostly due to the following missing feature #7633 (passing props as a parameter to the callback). Having a callback with the props of the component as arguments is something that will definitely enhance the readability of styling of components. As of right now, this is not yet the case, so the workaround is to make use of inline styling. Which is not the best solution, as we miss out on all the great jss plugin features such as auto-prefixer for example. For clarification, I'm also building an app, but that doesn't mean I don't want to create my own re-usable components. Edit: If it would be possible to pass both the props and theme to the callbacks of the individual css properties (such as react-jss) then remark becomes invalid. For example:
|
@TheHolyWaffle I agree that it would be useful to use callbacks for making use of props in styles... this discussion is specifically about the need for callbacks to obtain the theme. |
@TheHolyWaffle I guess this is a bit off topic, but anyway 🙂 The API your're describing is what Basically, you create all CSS classes beforehand and apply them later on based on props via helpers like @pelotom Had the same discussion at my company multiple times. If you're only consuming MUI standard components, there is no downside of your mentioned approach. At least none I can think off. If is only relevant for "3rd party" MUI components, like https://github.com/dmtrKovalenko/material-ui-pickers. |
I'm still getting my bearings with typescript, but I wanted to add that we have a few libraries that use Are you suggesting that the |
Not at all! I'm not actually suggesting anything should change about the library... if you're developing a reusable library of components, which it looks like this is an example of, then the callback method definitely seems necessary. |
FYI - so searchers may easier find this issue. I ran into this with a stateless functional component (gist): Error:(9, 3) TS2345:Argument of type '({ palette, shadows, transitions }: { direction: "ltr" | "rtl"; palette: Palette; typography: Typ...' is not assignable to parameter of type 'Record<"root" | "scrolled" | "avatar" | "content" | "contentSecondary", Partial<CSSProperties>> |...'.
Type '({ palette, shadows, transitions }: { direction: "ltr" | "rtl"; palette: Palette; typography: Typ...' is not assignable to type 'StyleRulesCallback<"root" | "scrolled" | "avatar" | "content" | "contentSecondary">'.
Type '{ root: { display: string; overflow: string; zIndex: number; backgroundColor: string; transition:...' is not assignable to type 'Record<"root" | "scrolled" | "avatar" | "content" | "contentSecondary", Partial<CSSProperties>>'.
Types of property 'root' are incompatible.
Type '{ display: string; overflow: string; zIndex: number; backgroundColor: string; transition: string; }' is not assignable to type 'Partial<CSSProperties>'.
Types of property 'overflow' are incompatible.
Type 'string' is not assignable to type '"initial" | "inherit" | "unset" | "auto" | "scroll" | "hidden" | "visible" | undefined'. which implies the inference failure. The fix being declaring an explicit type ClassKey = 'root' | 'scrolled' | 'avatar' | 'content' | 'contentSecondary'
const decorate = withStyles<ClassKey>( Thanks for the guidance @pelotom |
@rosskevin / @pelotom Would this be worth adding to the docs (Typescript guide)? |
@mbrookes yes, I'll try to add something when I get a chance. |
#7633 starts to be referenced by a lot of people. I will see if I can make it happen. As it was linked here. I'm eager to know if we can improve the DX and the performance with such feature.
@pelotom At work, we have been relying on the |
I'm lazy too! In TypeScript, it auto-imports when you start to type |
In that case, that we don't pass the theme via the context, What about: export default function withStyles<ClassKey extends string>(
style: StyleRules<ClassKey> | StyleRulesCallback<ClassKey>,
options?: WithStylesOptions
): <P>(
classes: WithStyles<ClassKey>
) => React.ComponentType<P>;
withStyles({root:...})((classes) => {
return function MyComponent() {
return <div className={classes.root} >MyComponent</div>
}
})
// or
withStyles({root:...}, (classes) => {
return function MyComponent() {
return <div className={classes.root} >MyComponent</div>
}
}) |
Or, why not just: const classes = withStyles({root:...}); |
Great questions... I've never really understood the need for the indirection of passing Another option that has been discussed is render props. |
A render callback component (I prefer using children as a function, not an unnecessarily verbose extra I recently switched my patterns to render callbacks, and while it has been great and works for most, it doesn't always work best for every scenario. Nonetheless I believe the render callback component should have the implementation, with the hoc being a convenience wrapper. |
Yeah, I'm a big fan of using class MyComponent extends React.Component<Props> {
render() {
return (
<WithStyles styles={styles}>
{classes =>
<...>
}
</WithStyles>
)
}
} ? |
Yes, where current args |
Maybe just |
This gives a natural way to accomplish a feature many have been asking for: styles that depend on props. Just move the definition of class MyComponent extends React.Component<Props> {
render() {
return (
<Styled styles={this.styles} options={options}>
{classes =>
<...>
}
</Styled>
)
}
styles = {
// ... can depend on `this.props`
}
} |
I agree with using Styled plus keeping withStyles name for the HOC. |
I have another iteration import { WithStyles } from "material-ui";
import { Theme } from "material-ui/styles";
import withStyles from "material-ui/styles/withStyles";
import * as React from "react";
interface IStyledProps<ClassKey extends string> {
children(style: WithStyles<ClassKey>): JSX.Element;
}
type stylesParamType<ClassKey extends string> = (
theme: Theme,
) => Record<ClassKey, React.CSSProperties> | Record<ClassKey, React.CSSProperties>;
export function createStyled<ClassKey extends string>(stylesParam: stylesParamType<ClassKey>) {
const wrapperComponent = withStyles(stylesParam);
const createdStyled: React.StatelessComponent<IStyledProps<ClassKey>> = function createdStyledComponent(props) {
return React.createElement(
wrapperComponent(stylePropsFromWithStyles => {
return props.children(stylePropsFromWithStyles);
}),
);
};
Object.defineProperty(createdStyled, "style", {
get() {
throw new Error("style prop is here only for TypeScript inference. use typeof Styled.style for the type");
},
});
// style is just for pulling the types
return createdStyled as typeof createdStyled & { style: WithStyles<ClassKey> };
} How to use: const styles = (theme: Theme) => ({
root: {
width: "100%",
background: theme.palette.background.paper,
flex: "1",
overflowY: "scroll",
} as React.CSSProperties,
preventInput: {
pointerEvents: "none",
},
});
const Styled = createStyled(styles);
function InnerComponent(props: { title: string; style: typeof Styled.style }) {
return <div className={props.style.classes.root}>{props.title}</div>;
}
function OutterComponent(props: { title: string }) {
return (
<Styled>
{s => {
return <InnerComponent {...props} style={s}/>;
}}
</Styled>
);
} |
@Bnaya If you build the component outside of the render method, in a static way, it should be good :). This means that we potentially have 3 different styling APIs. The implementation of the 2 other APIs is simple and short sugar on top of |
I'm gonna document the two styling API alternatives. |
I must point out that Conditional types are on the way, and will be with us in typescript 2.8 |
Should or is it still good practice to put full classes in it like that: export const theme = createMuiTheme({
palette: {
primary: blue,
},
card: { // some custom reusable classes
header: {
background: '...',
// ...
'&::before': {
// ....
}
},
progress: {
height: 64,
// ...
}
}
}); then in my components: const styles = ({ card: { header, progress }, palette }) => ({
header,
progress,
otherProp: {
color: palette.primary[600],
// ...
}
});
export default withStyles(styles)(Component) |
This question occurred to me while thinking about #9192, and I'll repost part of my comment there:
If one is not developing reusable MUI components, but instead making an app, is there any advantage to using
withStyles
with a callback, vs. exporting your theme from a module and then importing it everywhere that you want to use it? For example,This is particularly relevant to TypeScript for 2 reasons:
material-ui
issue tracker you'll find that this has been the source of all kinds of confusion. The workaround, which is not obvious at all, requires annotatingwithStyles
with a union of all your class keys, e.g.withStyles<'root'| | 'docked' | 'paper' | 'modal'>(/* ... */)
. Various complex solutions have been proposed for this, notably Add witness type for WithStyles<ClassKey> to return value of withStyles() #8829 and Better typescript and withstyles decorate #9105.Simply exporting a global theme and then importing it where you need it rather than using callbacks everywhere magically makes all these problems melt away.
It seems like the advantage of using a callback is that it makes your components more modular; you can ship them as a library and others can use them within any
MuiThemeProvider
. But in many cases people aren't making reusable libraries, they're making an app, in which case it seems like it's fine to just reference a global theme.The text was updated successfully, but these errors were encountered: