Radium: Media queries on component level flicker

Created on 14 Mar 2016  Â·  42Comments  Â·  Source: FormidableLabs/radium

When rendering components whose styles have media queries in them, flickering can be observed on the first render because at this point radium injects the media queries into the style tag. Not sure if this is a known bug and if so, is there a workaround?

bug high priority

Most helpful comment

any ideas how to fix this ?

All 42 comments

Is this because the style element is rendered after the component tree, so the components are rendered first and then repainted once the browser hits the style tag? (i.e. could we move the style tag placement and solve this?)

@alexlande I am not sure. I tried reordering the style tag within the chrome dev tools to see if it has any effect. I just moved the media query style tag above the div which gets updated but the result was the same - an observable flicker. I don't know if actually reordering it in code will yield different results, but worth a try. Could you point me to the line where the style tag is being rendered? Then I could play around a bit and see if I can get something working. If this does not solve it then it seems like media queries have to be rendered first and in a 2nd render pass the components should be drawn?

It's added here: https://github.com/FormidableLabs/radium/blob/b0fc3a36686eafa376faff475c1aad3c24a313be/src/components/style-root.js#L33-L40

Specifically, you would want to re-order these two lines:

{this.props.children}
<StyleSheet />

It would be nice if it was just a DOM order thing, but it sounds like that's not the case based on what you're saying.

@alexlande Just tested it out: it makes things worse. With those two switched there is an additional flicker after rendering - unfortunately.

Hmm... I'm not really having luck reproducing this. Do you have a minimal repro, by chance? Are you server-rendering your app, or is it only client-rendered? (I've only checked server-rendered ones, so it's possible that's the difference.)

@alexlande It's a server-rendered app. The flicker does not happen on the initial rendered page but when I transition to a different page which gets rendered on the client. The app uses mobile-first styles so for a split second I'm seeing the mobile styles and then the proper desktop styles.

I don't have a repo right now, but I probably could set one up over the weekend.

If you move the style tag after the rendered content you will break server
side rendering.
On Wed, Mar 16, 2016 at 1:16 PM Johannes Lumpe [email protected]
wrote:

@alexlande https://github.com/alexlande It's a server-rendered app. The
flicker does not happen on the initial rendered page but when I transition
to a different page which gets rendered on the client. The app uses
mobile-first styles so for a split second I'm seeing the mobile styles and
then the proper desktop styles.

I don't have a repo right now, but I probably could set one up over the
weekend.

—
You are receiving this because you are subscribed to this thread.

Reply to this email directly or view it on GitHub
https://github.com/FormidableLabs/radium/issues/626#issuecomment-197527480

I am facing the same issue with Radium. All the media-query less CSS gets rendered fine. But, for eg, if you have:

imageStyle: {
    '@media (max-width: 1000px)': {
        display: 'none'
    }
}

And you render this like so: <img src={..} style={styles.imageStyle}/>.

You will see this behavior:

  1. For screens <1000px width; this will render the picture on the first pass and disappear after a flash.
  2. For screens >1000px width; this will simply show the picture and you will see no FOUC.

This is because of how <StyleRoot> works. In it's description it says: StyleRoot wraps its children in a plain div followed by the root style sheet.. There in lies the problem. Since the style sheet gets rendered after the entire app, the elements which rely on media-query to style themselves experience a FOUC.

My HTML for example gets rendered like so:

<div data-reactid=".0.0">
    <!-- rest of my app is rendered on top here -->
    <style data-reactid=".0.0.$=1">
        @media (max-width: 100px){ .rmq-49c18b25{display: block !important;}}
    </style>
</div>

Would this be something the Radium team plans to address? It is a major blocker personally. For now, I am using a workaround of hiding the body until the document is ready. But this is a really bad hack for this problem.

@shilpan I was thinking about maybe writing a plugin, which sidesteps the react-rendering and instantly adds the media query to a style tag using stylesheet.insertRule. This is not optimal, but it might help with the issue. I haven't thought about performance, but basically it would have to do this for each component. So if we are rendering 30 components, where each of those have media queries, then that would result in insertRule being called 30 times. Maybe this can be mitigated using debouncing or something similar. Do you have any thoughts about this?
/cc @alexlande

@alexlande sorry couldn't set up the repo yet - busy ;(

@shilpan: I'm wondering if there are two separate issues here. The FOUC because the style element is rendered after the components is that I was getting at here: https://github.com/FormidableLabs/radium/issues/626#issuecomment-197404783, but @johanneslumpe reported that the problem was even worse after moving the style element because the styles were apparently not computed in time for the initial render.

@johanneslumpe: No worries!

@johanneslumpe : I personally think what @alexlande suggested should also solve the issue unless the styles are computed async. What you are recommending will still face the same issue since you will be adding the tag _after_ your component gets rendered. So you will still see that flash irrespective. Also I am not too sure about what @ianobermiller said regarding the server-side rendering, but that might be a big concern. I tried it and my server render did not break.

@alexlande Ah, I see. I am not too sure about the implementation details but are the styles getting computed asynchronously?

@shilpan I do not think that my proposed solution would suffer from FOUC, as it sidesteps react. Meaning as soon as it receives media queries, it will add them to the DOM using an imperative api, without waiting for react to do its own DOM updates.

What @ianobermiller referred to is the fact that if you swap those two components, you will always see a FOUC, no matter what. I swapped them and can confirm this from my own tests. (I didn't dig deeper to figure out why exactly this happens)

Swapping those two also had zero effect on a purely client-side app. It resulted in the same FOUC with mobile-first styles as before.

@alexlande I just tested out my proposed solution and it fixes the issue. Obviously this is just a proof-of-concept. Adding this after the addCss call in the _topLevelRulesToCSS function of the resolve-media-queries-plugin does the trick:

// `cache` and `stylenode` are variables in the outer scope
if (typeof window !== 'undefined') {
  if (mediaQueryClassName && !cache[mediaQueryClassName]) {
    if (!stylenode) {
      stylenode = window.document.createElement('style');
      window.document.head.appendChild(stylenode);
    }
    stylenode.sheet.insertRule(css, 0);
    cache[mediaQueryClassName] = true;
  }
}

Yes, it does have a crude check for window, but this really was just a quick test to see if it is a viable option. It does sidestep the StyleKeeper and maybe that's where we should start looking for a fi, but I assume this is something which can be discussed. At least now we know that this issue can be solved. (with fairly little effort) Thoughts?

I've checked the fix in IE 10-Edge, Chrome, FF, and Safari.

// cc @shilpan

@johanneslumpe your solution will not work at all with server-rendering in an environment without the DOM.

Also, the style element must be rendered after the rest of the content because on the server you only get one render pass; Radium depends on the entire component tree being rendered synchronously before the final style element.

@ianobermiller Yes it does, have you tested it? I did. Because of the check against the type of window it will work just fine on the server. I have specifically tested it against a server-rendered app. I made sure that this only gets executed on the client, because this is not an issue when server-rendering a page. Only when a component gets initially rendered on the client the flicker occurs.

@ianobermiller So did you give this another go?

@alexlande tried to reproduce this with a tiny example - couldn't, at least not consistently (sometimes I'd get a flicker in Chrome, but in no other browser). I'm inclined to suspect that this might only occur when there is a bit more workload which slows things down.. Cannot say it for sure though.

Any updates on this? I'm facing the same issue in chrome and firefox

I'm having trouble reproducing the flicker. I am assuming that it is an issue that appears due to some combination of root component complexity and machine speed.

@webyak Do you have a sharable component that has this issue? If not, could you try changing the affected project's package.json dependency to jmcriffey/radium#bug-foucFix and see if that solves the issue? Thanks!

I have the issue on my personal site (yannik.io). When you navigate to the about page you can see that the media query gets applied a friction of a second later on the heading.

@jmcriffey thanks! I tried to switch to your package but it didn't solve the issue.

Thanks for the link @webyak. I see the issue happening on your site, so that will help in trying to debug.

@johanneslumpe how are you dealing with this issue in your projects right now? Are you still using the fix mentioned above?

@alexlande @johanneslumpe @webyak I've set up a minimal repro here:

https://jsfiddle.net/fr5wce81/

If you press the button for the first time, you should see it flash with the default style. This likely has something to do with the style node, but I haven't yet pinpointed what exactly is happening. I have confirmed that changing the order of the style node and components does not solve the issue. I suspect some sort of React rendering magic could be the culprit. I'll play around with it a bit more and see if I can come up with a fix.

@jmcriffey hey, thanks for the fiddle!

When we break on div#react-root subtree modifications within the Chrome DevTools, we can see clearly that the h2 element gets added to the DOM way before the style element with the media queries gets updated.

screen shot 2016-06-28 at 09 41 08

screen shot 2016-06-28 at 09 32 57

Now the question is why?

_Update_: h2get's also added to the DOM first, even if switching {this.props.children} with <StyleSheet /> within Radium's StyleRoot.

@webyak I chose to not deal with it anymore and started searching for a different library due to several reasons.

@webyak Thanks for confirming that. I assumed something along those lines was happening. This seems to be either something with how React batches rendering or we are doing something weird within Radium itself.

I think I've found flaw. The styles of a Radium enhanced component get calculated during render, and when working with media queries the CSS of the media queries gets injected into StyleRoot.

_The problem lies in the way the CSS get's injected._ Whenever "new CSS" should be injected, the StyleSheet's _onChange method will be called to trigger a state change. But because this.setState can't be called during render it get's queued with a setTimeout(fn, 0), causing the Radium enhanced component to render before it's media queries get added to StyleRoot, resulting in noticeable flickers.

Not sure how to fix this properly right now. Would be awesome to have some help

Ah @webyak, good investigating. I'll look into this right now.

I have confirmed that removing the timeout fixes the issue. It looks like it was added to get around calling setState during a component's render and/or constructor. Unfortunately, calling setState without the timeout causes a nasty error message. See https://github.com/FormidableLabs/radium/commit/abfdcc7d0a798bdc2e97af3d72c9ead655c12dc0.

To briefly sum up the issue:

The StyleSheet component has a setTimeout in its _onChange method. This causes the inline styles to be applied first and then the style component updates its state, which causes the flicker.

I'll see what can be done to update the logic here so setState doesn't get called at the wrong time.

any ideas how to fix this ?

Has there been any progress on this?

I'm experiencing this issue too. Seems to be when components aren't initially rendered to the DOM. For example, a component that doesn't exist yet until after some state changes on the client. Any thoughts or solutions?

Been two months now, is there any update? I am starting a serious production app and need to know if Radium is even worth considering at scale, or if I am going to bump into nasty problems like this.

@johanneslumpe Why did you end up abandoning Radium, and do you suggest any alternatives?

@isaachinman Basically because I started to disagree with having all styles as inline styles. The media-query issue was a big part of it. Also, simulating hover, etc in JS is just a pain. I've moved on to using https://github.com/rofrischmann/fela and I'm quite happy with it.

In terms of scale: Radium did slow our codebase down a bit, but YMMV. In addition to that, the bundle size isn't small either.

This is a big problem for react apps. The flash of wrong content styles looks really bad and jarring, in some cases like an error happened. I'm going to have to look into solutions other than Radium as well.

Not to beat a dead horse but I'm having a new issue around this where safari in iOS 10 doesn't seem to repaint the component after the styles are added. What that means is instead of a flash of unstyled content it is sticking unstyled. This is on a form that is rendered after the user clicks an edit button. The first time it is opened it is a partially styled mess, but if it is hidden and then opened again it renders fine (presumably because the CSS styles are already there when it mounts the second time).

Is anything going to be done to address getting the CSS in the page before the component renders?

I'm experiencing sporadic FOUC due to the injected style tag coming at the end of the document.
I realized that this was the problem after running my page through https://validator.w3.org/ that informed me that I had a style tag where it's not allowed. Then I checked the Radium home page and saw that it too failed in the validator.

I ran into this same problem and wrote this little diff to try https://github.com/paulshen/radium/commit/080656448. The issue was with the setTimeout as pointed above. I haven't tested this thoroughly but it seems to work.

If you're curious, the diff makes it so <StyleSheet> doesn't use React's state for updating StyleSheet so the setTimeout isn't needed.

Hi folks, have there been any new talks around this issue? We experience several FOUC problems due to this - basically anytime we use media queries or key frames. We use Radium for our component library, and this has been our only large issue.

Okay, so it looks like we have a path forward here. To summarize:

The underlying problem ultimately exists because Radium does its deep magic during React’s render pass. To use media-queries, we have to:

  • stick a class onto the object which wants a media-query
  • render the style rules to a CSS string
  • wrap that in a selector for that class
  • wrap THAT in the media-query
  • flush it all out to a <style> tag.

The FOUC happens because of _when_ we flush everything to the style tag.

Radium's StyleSheet component currently stores the desired contents of the <style> tag in its state. When Radium wants that to change, it updates it using setState. However, the code currently defers this call to the next tick using setTimeout because React complains if you call setState synchronously during a render pass.

By manually setting the contents of StyleSheet on the DOM itself, we can get rid of the setState call, and therefore avoid the associated delay which causes the FOUC. I set up a little testbed, added the patch from @paulshen and confirmed that it fixes the problem: the media-query styles get added before React starts rendering the elements which use them, and everyone’s happy.

I _think_ this resolution is acceptable, with the caveat that it _does_ end up making us touch the DOM every time a plugin wants to flush styles out to the StyleSheet (i.e. we lose batching). @alexlande can you weigh in on this and let me know what you think?

In the meantime, I'm gonna go ahead and create a PR to patch in @paulshen's changes to StyleSheet.

Was this page helpful?
0 / 5 - 0 ratings