styled-components: Global styles does not work in React.StrictMode

Environment

System: OS: macOS 10.15.1 CPU: (8) x64 Intel® Core™ i7-8569U CPU @ 2.80GHz Memory: 857.00 MB / 16.00 GB Shell: 3.2.57 - /bin/bash Binaries: Node: 10.15.3 - ~/.nvm/versions/node/v10.15.3/bin/node Yarn: 1.16.0 - /usr/local/bin/yarn npm: 6.4.1 - ~/.nvm/versions/node/v10.15.3/bin/npm npmPackages: styled-components: ^5.0.1 => 5.0.1 Reproduction https://codesandbox.io/s/wonderful-hill-6glof

Steps to reproduce

Load the code sandbox Click the button “to black” and then click again “to white” and notice how the background did not change back to black.

Expected Behavior

The should change between black and white

Actual Behavior

The background stays black after the first press on the toggle button. The global CSS (rendered by GlobalStyle in ComponentBlack) is added twice and only one is removed when unmounting when in Strict Mode. This is because to Strict Mode “intentionally double-invoking” some lifecycle methods to determine side effects that could be a problem in the upcoming async mode in React. When concurrent mode becomes available and React might call render function multiple times the side effect of setting global styles on render will cause issues like this.

As far as I can tell styled-components should be React.Strict compatible and using the createGlobalStyle util is the recommended way to do global styling with styled-components, so this seems to be a bug to me.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 2
  • Comments: 15 (4 by maintainers)

Most upvoted comments

Hi, I can see it has been a while but I think I am facing a similar issue with the latest styled-components and latest nextjs with reactStrictMode: true defined in next.config.js

Here are the reproducing steps;

Go to latest example provided by nextjs with styled components https://github.com/vercel/next.js/tree/master/examples/with-styled-components

You can install project with the following script

npx create-next-app --example with-styled-components with-styled-components-app

Once porject is created go to top level and add next.config.js with the below content

module.exports = { 
  reactStrictMode: true,
}

Run development server by

npm run dev

Check body styles and styled components style tag in head. You will see body styles are duplciated.

image

Environment

System: Mac Mini (M1, 2020) OS: macOS BigSur 11.4 CPU: Apple M1

Shell: zsh 5.8 (x86_64-apple-darwin20.0) Binaries: Node: v16.0.0 npm: v7.10.0 npmPackages:

{
  "next": "latest",
  "react": "^17.0.2",
  "react-dom": "^17.0.2",
  "react-is": "^17.0.2",
  "styled-components": "^5.2.3",
  "babel-plugin-styled-components": "^1.12.0"
}

Can y’all try out styled-components@test and let me know if it resolves the StrictMode issue?

styled-components@5.2.0-test.10 fixes the issue for me, good job. Cheers! 🍻

Same here. Ended up moving global styles outside of React.StrictMode:

<GlobalStyle />
<React.StrictMode>
    <App />
</React.StrictMode>

instead of

<React.StrictMode>
    <GlobalStyle />
    <App />
</React.StrictMode>

Can y’all try out styled-components@test and let me know if it resolves the StrictMode issue?

In React.StrictMode this function returned in the useEffect doesn’t run when strict mode “mounts” the component a second time which causes the issue because the instance.value increments, but previous styles do not get removed.

React doesn’t mount the component twice. It renders it twice. Only for the committed render an effect will run (and its cleanup function after that commit is discarded).

I feel like this issue is specific to React.StrictMode? Is there ever a scenario that the returned callback from useEffect would not run when the component dismounts (even in concurrent mode)?

Today you can reproduce this realiably if you have a component in a suspense boundary where a sibling suspends i.e. the component renders, the tree suspends, react discards the render and does not commit it: https://codesandbox.io/s/concurrent-mode-suspended-component-commit-lnndy

This can also happen if a low priority render is interrupted by a high priority update but that’s a bit harder to reproduce reliably.

Be careful with actually doing side-effects in that demo and logging them. react@experimental in DEV mode will still invoke renders twice but will disable logging for the second invocation. Logging states affected by side-effects is only reasonable in production builds.

The issue in #3022 doesn’t just apply to Next.js, it also affects Jest tests and custom SSR solutions.