cssinjs / jss

JSS is an authoring tool for CSS which uses JavaScript as a host language.
https://cssinjs.org
MIT License
7.07k stars 397 forks source link

[react-jss] Sometimes `<style>` is injected after first render — which makes CSS transitions fire immediately #1324

Open SimpleCreations opened 4 years ago

SimpleCreations commented 4 years ago

Describe the bug: I have encountered this bug in production and tried to reduce the example as much as possible. Under certain conditions CSS transitions get triggered when page loads.

See Codesandbox link. When the page loads, you can see red text appearing on the page which shouldn't be there.

As far as I understand the issue, <style> element gets injected after the component renders and not before — I tried console-logging the <style> element and it is indeed not there when the functional component (Styled) get called the first time.

This does not affect the app behavior unless a reflow is triggered. Upon reflow, the browser apparently "remembers" the default style values (in my case opacity: 1) and then applies transitions once JSS's <style> is injected. I my example, I added a call to getComputedStyle (which triggers reflow) and put it in componentDidUpdate method of a class component, which is rendered but outputs nothing. For some reason I was unable to reproduce this using hooks.

The bug was introduced in 10.0.0. Downgrading to 9.8.7 resolves the issue.

Codesandbox link: https://codesandbox.io/s/jolly-booth-82tke

Versions (please complete the following information):

kof commented 3 years ago

I just looked into it, what is happening is essentially this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
  </head>

  <body>
    <script>
      const div = document.createElement("div");
      div.className = "vanilla";
      div.textContent = "vanilla";
      document.body.appendChild(div);

      getComputedStyle(document.documentElement).height;

      const style = document.createElement("style");
      style.textContent = `
        .vanilla {
          color: red;
          font-weight: bold;
          opacity: 0;
          transition: opacity 10s;
        }`;
      document.head.appendChild(style);
    </script>
  </body>
</html>

componentDidUpdate seems to be firing before we render styles, but we render styles right after dom is rendered, in sync componentDidMount, which is normally not a problem, because styles are not going to be calculated in between.

So you found a way to recalculate styles before they have been rendered.

kof commented 3 years ago

This is an interesting problem, because I think React has currently no other way to mount styles before DOM so that there is no way to cause recalc in between

kof commented 3 years ago

Another finding is that this problem does not appear to be exist when using hooks based api - https://codesandbox.io/s/jolly-booth-82tke?file=/src/App.js

Which means componentDidUpdate in this case doesn't execute between dom and styles render.

kof commented 3 years ago

This brings me to the issues we discussed somewhere - we need to rewrite withStyles to use hooks api internally and the problem will disappear.

kof commented 3 years ago

Whoever would like to solve it, let me know.

WalterWeidner commented 1 year ago

I suspect we are seeing a similar issue with one of our components that uses Framer Motion.

We've previously been using MUI's makeStyles() custom implementation of JSS and have investigated replacing it withreact-jss instead (since they've changed their styling solution yet again). From what I understand MUI injects stylesheets during the component render and JSS injects it immediately after using useLayoutEffect() when using React 17 and not using SSR.

I haven't dug extensively through both of your repositories but I did play around a bit with the the implementation of createUseStyles() to try to make it more like what MUI is doing. If I tweak createUseStyles() to manage stylesheets during the render (like MUI does) it fixes our issues with Framer Motion. In this case there's a child component (Grid Item) that is impacted by the styles of its parent component (Grid) styles. This causes it to initially animate like below when mounting when it shouldn't.

2022-11-04 14 06 45

WalterWeidner commented 1 year ago

Any thoughts on this @kof ? I am happy to help but I don't have context for the reasoning behind your implementation (using useLayoutEffect(), for example).

Omaksousa commented 1 year ago

I am facing the same issue any update guys?