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
Use new context API #1894
Use new context API #1894
Conversation
Good start! Let's remove that At this point for 4.0 I think we will just plan to drop old React support and just target 16.4+ to keep things easy. |
Oh I actually just forgot to use the Cool, nice to save some KBs as well! 👍 |
Let's use |
Okay, although I think using the memoized approach is a bit cleaner and simpler, because then you don't need to recalculate in Edit: Btw I think this is really useful info https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#what-about-memoization |
I think the Switched the test to Left the memoization for now but should be fairly simple to change if you'd still like that |
src/models/StyledComponent.js
Outdated
@@ -110,7 +87,8 @@ class BaseStyledComponent extends Component<*, BaseState> { | |||
|
|||
generateAndInjectStyles(theme: any, props: any) { | |||
const { attrs, componentStyle, warnTooManyClasses } = this.constructor | |||
const styleSheet = this.context[CONTEXT_KEY] || StyleSheet.master | |||
// const styleSheet = this.context[CONTEXT_KEY] || StyleSheet.master | |||
const styleSheet = StyleSheet.master |
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'm unsure what this previously was for, but I think it will be fine just assigning it to Stylesheet.master
?
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.
@kitten can you comment on this? I think it's okay
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 just realized that that context comes from the StyleSheetManager
, so I'll have to wrap it in a StyleSheetConsumer
or similar to get the correct value
src/models/StyledComponent.js
Outdated
return createElement(target, propsForElement) | ||
return ( | ||
<ThemeConsumer> | ||
{({ theme } = {}) => { |
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.
Can you use the empty object from utils/empties?
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.
Can we also cache this function somehow instead of recreating it every render?
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.
If I use EMPTY_OBJECT
I get a flow error though, so the best way might create a type
for the context and not destructure it, right? I've not really worked with flow before so I might need some help on certain bits
Yeah good point, although I don't it's that expensive to recreate it
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.
It's more about memory pressure. This is a hot path, so we want it to be as maximally-efficient as possible.
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.
Makes sense, will refactor to take this into account!
src/models/ThemeProvider.js
Outdated
getContext(theme: (outerTheme: ?Theme) => void, outerTheme?: Theme) { | ||
return { | ||
theme: this.getTheme(theme, outerTheme), | ||
} |
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.
What if we instead of using setting context to { theme }
we set it to theme
without a wrapping object, we won't be able to add any other properties on the context but I don't think we will want that anyways?
|
Tests are passing 🎉 Would love to get some second opinions and feedback on this now @kitten @mxstbr @probablyup |
Nice! Ok so the last thing I want to do here is some performance analysis. Specifically making sure we aren't regressing against current master. Could you rig up a test using ThemeProvider in the benchmark suite (it's in this repo under benchmarks.) |
Let's also switch this branch to be pointing at "develop" which is the 4.0 development branch I just made. |
Okay so I’m not sure that I interpret the benchmark results correctly, is a high or low number better? First thought it was high=better but I just noticed the ms suffix which made me uncertain Anything you want me to fix before this can get merged? |
Lower ms is better. I think @kitten is right though, there are a bunch of other changes we're going to make in this branch so perf analysis might be premature. |
package.json
Outdated
@@ -150,7 +148,7 @@ | |||
"typescript": "^2.4.1" | |||
}, | |||
"peerDependencies": { | |||
"react": ">= 0.14.0 < 17.0.0-0" | |||
"react": ">= 16.3.0 < 17.0.0-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.
Let's just make this >= 16.3
for simplicity.
return React.createElement(Component, props) | ||
return ( | ||
<ThemeConsumer> | ||
{(theme?: Theme) => { |
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.
Let's cache the function on the class so it's not getting recreated per render.
|
||
return ( | ||
<StyleSheetConsumer> | ||
{(styleSheet?: StyleSheet) => ( |
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.
Same here, let's cache it on the class.
return createElement(target, propsForElement, children) | ||
return ( | ||
<ThemeConsumer> | ||
{(theme?: Theme) => { |
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.
And here
|
||
constructor(props: Props) { | ||
super(props) | ||
this.getContext = memoize(this.getContext.bind(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.
Don't need to do constructor
if you use this pattern on the class itself:
class ThemeProvider extends Component<Props> {
getContext = memoize(
(theme: (outerTheme: ?Theme) => void, outerTheme?: Theme) => this.getTheme(theme, outerTheme)
)
}
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.
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.
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 can leave it though, I'll experiment with it both ways later
This is looking great! Let's do this final cleanup pass and get it in! |
Cool, in that case this PR actually outperforms the master branch, correct? So I tested to use class properties instead of binding the function in the constructor and it performed worse :/ Feel free to try this yourself as well, in case I did something wrong. Also tested to break out the inlined functions inside the render method, and that didn't seem to make any notable difference other than we run into the same problem described above. It might be easier to get this in the develop branch and then iterate on these things? |
@alexandernanberg Yeah that's possibly true! We did make a lot of changes. |
@@ -2,117 +2,93 @@ | |||
/* eslint-disable react/no-multi-comp */ | |||
import React from 'react' | |||
import { shallow, render, mount } from 'enzyme' | |||
import ThemeProvider, { CHANNEL_NEXT, CONTEXT_CHANNEL_SHAPE } from '../ThemeProvider' | |||
import ThemeProvider, { ThemeConsumer } from '../ThemeProvider' |
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.
Maybe instead of ThemeConsumer
we should test this with the withTheme
HOC and normal styled
constructors instead. That way we are ensuring the external API doesn't change.
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.
Okay so would this be OK or do we want to use expectCSSMatches
function as it's done in theme.test.js
?
it('should merge its theme with an outer theme', () => {
const outerTheme = { main: 'black' }
const innerTheme = { secondary: 'black' }
const childrenSpy = jest.fn()
const MyDiv = ({ theme }) => {
childrenSpy(theme)
return null
}
const MyDivWithTheme = withTheme(MyDiv)
render(
<ThemeProvider theme={outerTheme}>
<ThemeProvider theme={innerTheme}>
<MyDivWithTheme />
</ThemeProvider>
</ThemeProvider>
)
expect(childrenSpy).toHaveBeenCalledTimes(1)
expect(childrenSpy).toHaveBeenCalledWith({
...outerTheme,
...innerTheme,
})
})
I'm happy with this once the last testing point is resolved. Excellent work 👊 |
Superb! Thank you 😄 |
It's going in! |
Thank you so much for helping us improve styled-components! Based on our Community Guidelines every person that has a PR of any kind merged is offered an invitation to the styled-components organization—that is you right now! Should you accept, you'll get write access to the main repository. (and a fancy |
This is just to get the ball rolling with the new context API
Fixes #1459
Fixes #1655
Pretty happy with how the
ThemeProvider
turned out, a lot cleaner. I'll continue working on this and hopefully fully landing this PR.Enzyme does not support the react > 16.3 yet so it's hard to test this until that gets fixed. We probably want to use a polyfill for this as well, like create-react-context unless we drop React < 16.3 supportNo hard feelings if you decide not to use this!