-
-
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
[POC] Styled render callback component and refactored withStyles #9503
Conversation
src/styles/Styled.js
Outdated
} | ||
|
||
warning( | ||
indexCounter < 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
< 0 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This warning isn't that useful. It' in case you called the component many many times. But would have probably found the issue before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still, the warning doesn't harm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an API people have been asking for different time in the past. I like the flexibility compound component provides. I would vote for keeping both API. I have been giving some thought on the tradeoff both API offer on an old issue
On a side note. We need to be cautious with this new API. I think that we should keep using the HOC API. I don't want newcomers to ask themselves which API they should be using. What's the best one? This is unneeded complexity for newcomers. It doesn't matter for them. Just use one and stick to it.
@@ -1,11 +0,0 @@ | |||
// @flow | |||
|
|||
module.exports = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? Isn't it better to have all our configuration in js?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Easier to use yml, it's text only. Shorter file name. Dot file name, more hidden in the src tree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My order of preference for configuration files is: js > yml > json
. But as you prefer :).
src/styles/Styled.js
Outdated
} | ||
|
||
warning( | ||
indexCounter < 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This warning isn't that useful. It' in case you called the component many many times. But would have probably found the issue before.
src/styles/withStyles.js
Outdated
return <Component classes={classes} {...more} {...other} ref={innerRef} />; | ||
} | ||
} | ||
|
||
Style.propTypes = { | ||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, the propTypes ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
innerRef
is exclusive to withStyles
, classes though can be omitted. --Actually, I don't think we need any (including innerRef
), just pass-through now.
src/styles/withStyles.js
Outdated
'Material-UI: you might have a memory leak.', | ||
'The indexCounter is not supposed to grow that much.', | ||
].join(' '), | ||
const Style = () => ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We almost never use anonymous arrow function for the components definitions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, why not? (curious) What do you prefer here?
src/styles/Styled.js
Outdated
} | ||
|
||
class Styled extends React.Component { | ||
static defaultProps = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have been removing all the static properties as they get in the way of refactoring components from classes to functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't do that if it is still a class, it is invalid typescript and could harm our potential conversion. If it's a class, static props are the right way, the other way is just a hack after all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, TypeScript doesn't support this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have to: augment the definition of the target, or cast it. So it supports it, but you have to cast it to the type first. In other words it is uglier on the outside, vs static class prop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright. At least I can keep using this pattern at work. It's saving us time refactoring component class <-> function
.
src/styles/Styled.js
Outdated
} | ||
|
||
warning( | ||
indexCounter < 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still, the warning doesn't harm.
@kof @oliviertassinari perhaps you might have a thought on this one. When using let sheetManager = this.sheetsManager.get(this.stylesCreatorSaved);
if (!sheetManager) {
sheetManager = new Map();
this.sheetsManager.set(this.stylesCreatorSaved, sheetManager);
} where This is a show stopper if not solved because it causes massive sheet growth. ^^^Next question: If this can be solved, won't we still have the same growth issue with interpolating props into styles? If you want to run it - look at |
I’m wrong about the props being modified - I committed a hack that proves it. It is But, my comment about sheet growth with props interpolation is something to be weary of with this approach. Moving forward again... |
So it appears that the result of: |
I think this may have been the source of the need for the hot loader hack. Maybe... just speculating at this point. |
…es input. This is important - because it is the source of the sheet leak - the result of this is used as the key for the sheet manager
Targeting the As @oliviertassinari said in chat:
|
Ok @oliviertassinari @pelotom - I've got to go for the night. I've got a full day into this (plus a little yesterday). Two things need to be considered:
Any thoughts are welcome. I think this is a decision point - I need to confirm before spending more time. |
}} | ||
</Styled> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an example where the render callback pattern is better. withTheme
and innerRef
are HOC options not necessary using Styled
. With a render callback pattern, the child chooses what it uses and what it ignores (e.g. theme
), and the innerRef
indirection is unnecessary because the parent can put the ref
where ever it chooses. This code shows what it took to match the various current usage patterns of the HOC.
We should switch to react-jss, really, maintaining both projects with edge cases bugs is hard. I know there are some things missing, we need to figure out how to add them. |
@kof several thoughts come to mind:
A cursory review of react-jss seems to share many similarities, but the devil is in the details.
|
We talked with @oliviertassinari many times and he created a bunch of issues on react-jss in order to move most parts of the logic at some point (most of them have been already resolved and released). In order to move faster, it is easier to implement things in mui directly first and then find a way to add them to react-jss if they make sense for more general use cases. MUI might still have very specific requirements, where react-jss can't or shohuldn't cover them. React-JSS has a render callback interface issue for a long time now, it was never clear why we would want that, it still isn't, but it is certainly an interesting interface. I am always hesitating to introduce an alternative interface, because it will make users to choose, without having a very clear explanation of "why". |
/cc @oliviertassinari - I forgot that I'm so far down the road of static typing, that others might not recognize the pain of HOCs. So @oliviertassinari, some of this is I owe to you based on our chat yesterday. Users can choose one or the other, where it suits their use case. In my code (for styles), I would use the render callback pattern exclusively because styling is a render-only use case - in other words - none of the props injected have any bearing on the rest of the implementation of the components as they are only needed to render (e.g. not needed in Render callbacks are significantly less static type coupling and coupling in general:
I need no more convincing that this pattern is dramatically easier to create, maintain, and use. BUT, with that said, there are cases (probably 10%) where a HOC is a nicer pattern of use (albeit not a better pattern to have to maintain). Since any render callback pattern can also be easily wrapped and exposed as a HOC, it seems like a no-brainer to do. |
@rosskevin yeah, sounds very convincing as you say it, worth a blog post with all the details! Now I am actually more motivated to add it to React-JSS. |
So @kof - is react-jss going to implement it - or do we just continue here on our own? |
Would you like to try add it to react-jss? I haven't thought much about technical implications or challenges yet, but it would be certainly worth trying. |
Edit - all of the above no relevant, my mistake, <Styled styles={myStyles}>
{({ classes }) => (
<Observer>
{() => <div className={classes.root}>{model.name}</div>}
</Observer>
)}
</Styled>; |
@Bnaya note that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just want to note here that unfortunately I don't think Styled
will be very usable in TypeScript until microsoft/TypeScript#6395 is solved. The reason is that the Styled
component needs to be generic in the keys of its style
prop. IOW, you want to give it a type like
interface StyledProps<ClassKey extends string> {
styles: Record<ClassKey, React.CSSProperties>;
children(classes: WithStyles<ClassKey>): JSX.Element;
}
class Styled<ClassKey> extends React.Component<StyledProps<ClassKey>> {
// ...
}
but then when you go to use it there's no easy way to instantiate the generic parameter, and type inference won't help you:
// doesn't work--`ClassKey` can't be inferred
<Styled styles={myStyles}>
{classes => /* ... */}
</Styled>
// also doesn't work--syntax error
<Styled<keyof myStyles> styles={myStyles}>
{classes => /* ... */}
</Styled>
I'm closing this out (for now):
I'm still firmly for using this pattern, but I'm not sure the best place to do it is here vs react-jss, as well as how it impacts types (which I really like the validation as it sits today). |
@rosskevin Thanks for working on the POC. The conclusions are great starting points for anyone who would like to push the effort forward! |
interface StyledProps<ClassKey extends string> {
children(classes: WithStyles<ClassKey>): JSX.Element;
}
function createStyled<ClassKey extends string>(stylesParam: Record<ClassKey, React.CSSProperties>) {
return class InnerStyled extends React.Component<StyledProps<ClassKey>> {
}
}
const Styled = createStyled({elementA: {}, e2: {}});
const b = <Styled>
{(styles) => {
return <div className={styles.classes.elementA}><a className={styles.classes.e2}></a></div>
}}
</Styled>; @rosskevin The above pattern will work type safely also now. |
I've made this function, would like to hear your feedback 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(classes: 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);
}),
);
};
return createdStyled;
}
const styles = (theme: Theme) => ({
root: {
width: "100%",
background: theme.palette.background.paper,
flex: "1",
overflowY: "scroll",
} as React.CSSProperties,
});
const Styled = createStyled(styles);
const a = <Styled>{(styleLocal) => {
return <div className={styleLocal.classes.root}>Test</div>;
}}</Styled>; |
@Bnaya I have been implementing something close in https://gist.github.com/oliviertassinari/dceb407e99d7279cc4228957c068d27c. It's a styled-components like API. |
What's the CSS precedence issue? |
@Bnaya You can use this API outside of a React render method, you can't use it inside. If you do, the injected CSS won't get precedence on the children you are trying to customize. |
Can you link to the relevant issue (if any)? |
@Bnaya There is no "opened" issue for this, nor link as it's pure speculation from my experience. If you build the component outside of the render method, in a static way, it should be good :). |
DO NOT MERGE, for review/feedback only
POC towards #9272 (comment)
related: #8542 #8447 #7636
Approach
Move the implementation of
withStyles
toStyled
and retainwithStyles
API exactly as it has been.Deficiencies
REVIEW
indexCounter
could be irrelevant now?Usage
current
withStyles
HOC or new render callback: