-
Notifications
You must be signed in to change notification settings - Fork 262
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
Fix JSXStyle renders styles too late #484
Conversation
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.
Hm. This looks unsafe for the reasons mentioned here.
getSnapshotBeforeUpdate
is meant to be read-only, but...I wonder if you considered trying it for the stylesheet modification? At least it would only be called during (right before) commit.
} | ||
|
||
componentDidUpdate(prevProps) { | ||
styleSheetRegistry.update(prevProps, this.props) | ||
// Remove styles in advance. |
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 explain what this means or what it's doing?
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.
On update we then remove the old styles early enough aka on getSnapshotBeforeUpdate.
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 the case when you have dynamic styles i.e. interpolations (tmpl literals expressions) that are based on props
My guess is that because this component is a child of the elements it styles, the styles are rendered slightly late (ha ha). Anyhow if sCU is solid this should render (as for render is called only once during the render phase) only once right?
I could do that too, would it be safe to keep a class properly to track the |
Still doesn't seem right to me. Changes to the DOM are committed synchronously, so there should only be one layout+paint. 😕
No. Render can be called multiple times– if there's an error, or if your app is using the new async rendering mode.
Yes, assuming you only set that property in
For now |
Yeah I will try harder and see if I can manage to make a test case.
So it seems that for our case the safest thing to do is to have all the side effects in |
@bvaughn what I don't understand is this part:
Is everything committed after |
No. The DOM is mutated before This is the reason that cascading updates (re-renders caused by e.g. |
interesting, it is not quite clear to me how layout and paint happen after |
i mean aren't mutations synchronous as well (i.e. might trigger a layout/paint)? |
Reading certain properties will force the browser to do sync layout before returning a value |
Here's a list @paulirish created some time ago of which property reads trigger the browser to synchronously calculate the style and layout
If React has mutated the DOM in such a way as to have invalidated layout or styles, and you force layout/reflow by reading one of these properties, then yeah– the browser would synchronously recalculate things. |
@bvaughn I figured out a way to replicate https://twitter.com/giuseppegurgone/status/1041319367886221312 |
This reverts commit 5ddef29.
@bvaughn for this specific use case would it be ok to keep the side effect in render() {
const stringifiedProps = this.props.styleId + String(this.props.dynamic)
if (this.stringifiedProps !== stringifiedProps) {
this.stringifiedProps = stringifiedProps
styleSheetRegistry.add(this.props)
}
return null
} the comparison is a guard to make sure that |
All side effects are potentially dangerous. In the case of updates, my best guess for when styles could safely be added is still Unfortunately it wouldn't work for the initial render nor for the server-side rendering case. I don't really have a good solution for those to be honest. |
I suggest you all might create an RFC for this. This one I created a while ago could help with your server side rendering case but it wouldn't help with the client-side mount case: |
@bvaughn thank you, that's precious information! |
I have some good news for you! We are working on something (no RFC yet– but hopefully soon) that should solve the mount+update mutation timing issue we've been discussing here. I hope to share more soon! |
aw nice, can't wait to see what you came up with! (happy to help if necessary) |
I was looking at this code trying to figure out some concurrent mode compatibility issues. This fix is not concurrent mode safe (for example, it could remove things before the new changes have committed). However, we'd also expect this to cause bad perf with concurrent mode since you'd have more relayouts while React yields. I was able to repro in React by simply adding a previous sibling like this (the codesandbox is broken for me right now so I can't fork it): <div ref={n => { if (n) n.clientWidth; }} /> This forces a layout. This seems to trick Chrome into treating this as already mounted. Any subsequent mutations then cause a CSS transition to happen. What happens in this case is this: 1) React adds the DOM nodes to the document. 2) Something reads layout in a different life-cycle/effect. 3) React fires JSXStyle's life-cycle which then inserts the style sheet into the document. This causes Chrome to think this is an update to an already visible component and triggers a transition. Now even if this was fixed in browsers, this sequencing would still leave the second step to potentially read the wrong layout. We originally planned to add something like So this is missing a good solution atm. |
@sebmarkbage this is an intermediate patch, the final version where I tried to make it concurrent mode safe is here https://github.com/zeit/styled-jsx/blob/343795d3b2fef6d3d085921b0d4cf31b4d9be709/src/style.js but back then my understanding of all this was very poor and I haven't looked into it ever since. |
Leaving a link to a reproduction for future reference https://codesandbox.io/s/react-17-repaint-demo-forked-c716t?file=/src/App.js |
|
We tried master on zeit.co and it seems that when we have the side effects in
componentDidMount
styles are rendered after the content causing transitions to apply to properties that wouldn't otherwise be animated e.g.padding
similarly to this https://jsfiddle.net/uwbm165z/ (unfortunately I wasn't able to reproduce with React yet but here is my attempt https://codesandbox.io/s/p7q6n6r35m).In this patch we move the side effect
styleSheetRegistry.add
torender
making sure thatshouldComponentUpdate
doesn't trigger unnecessary re-renderings. On update we then remove the old styles early enough aka ongetSnapshotBeforeUpdate
.@bvaughn this is a "followup" of the conversation we had in #457 (comment) Does it make sense?