next.js icon indicating copy to clipboard operation
next.js copied to clipboard

Wrapped styled-components don't update on Fast Refresh of imported modules

Open andrewgies17 opened this issue 4 years ago • 9 comments

Bug report

Describe the bug

When a styled component is created by wrapping another styled component, as in:

const Wrapper = styled(AnotherStyledComponent)``;

updates to the wrapped component aren't properly applied during Fast Refresh, when AnotherStyledComponent is imported from a separate module, and that module exports only React components. More info (and theories) at the end of this issue.

To Reproduce

Minimal reproduction at: https://gitlab.com/andrew.gies.axiom/next-sc-bug The reproduction repo is based on the official example for using styled-components.

Can be run using yarn dev or npm run dev and opening localhost:9000. Open lib/components.js and change the background color of the StyledThing component - notice that components.js is reloaded and re-evaluated by checking the browser console, yet the new styles aren't applied to the page.

Uncommenting the export const RandomNumber = 5 line will cause the Fast Refresh/HMR behavior to work correctly upon changes to StyledThing.

Expected behavior

When the styles are changed in code, they are updated in the browser after a Fast Refresh/HMR without needing to reload the page.

System information

  • OS: macOS Mojave 10.14.6
  • Browser: Chrome 84.0.4147.125
  • Version of Next.js: 9.5.2
  • Version of Node.js: 12.18.2

Additional context

This bug was introduced in next.js 9.3.7-canary.19 (not reproducible in 9.3.7-canary.18), which leads me to believe that it is an issue with Fast Refresh. (Fast Refresh was enabled by default in canary 19). It is reproducible on the current latest version of next as well (9.5.2).

Styled components can "wrap" other styled components to create extensions of their styles. This doesn't actually create a component tree that contains a wrapper component and a wrapped component; instead, styled-components creates a new component that duplicates the old component, but with the new styles added. When one of the "wrapped" components changes, the changes are not properly updated if only the "wrapper" is actually being used in component tree.

The below refers specifically to the example in my minimal reproduction repo, above.

For example, running in dev mode and changing the background of StyledThing in components.js will not properly update on the webpage. In the browser console, we can see that components.js is being reloaded and re-evaluated, but index.jsx is never reloaded, and so the updated component is never re-wrapped into Wrapper in index.jsx and inserted into the React tree. If anything that isn't a React component is exported by components.js, then this issue no longer exists. (However, multiple react components can be exported with the same issue).

My guess is that roughly the following logic is being applied to cause the bug:

  • Fast Refresh is noticing that components.js needs to be reloaded, grabs the new file and re-evaluates it.
  • It sees that the only things exported by this file are react components, so it'll attempt to update only parts of the existing React tree that are using these components.
  • Since StyledThing doesn't appear anywhere in the React tree, nothing is updated and index.jsx is not re-run, so the changes are never applied.

However, when something else (like a number) is exported from components.js, the following happens:

  • Fast Refresh is noticing that components.js needs to be reloaded, grabs the new file and re-evaluates it.
  • At least one non-React-component thing is exported, which means it may have effects on files that import it beyond just changes to the React tree.
  • To play it safe, Fast Refresh falls back to the more "traditional" HMR behavior of re-evaluating all modules that import components.js, which includes index.jsx.

The mistake is in the assumption that the only effect a React component can have on a program is if it is inserted into the component tree, and that the component can be entirely ignored if not used in that way. While a mostly reasonable assumption, this breaks in situations like styled-components wrappers where components are used indirectly, having an effect on the program execution without ever being inserted into the tree.

andrewgies17 avatar Aug 18 '20 19:08 andrewgies17

facing this in v9.5.4 as well

sreetamdas avatar Oct 14 '20 15:10 sreetamdas

Me too, i cant extend. My Button file:

import styled from 'styled-components';

const Button = styled.button`
    background-color: #00ade6;
    border: 1px solid #00ade6;
    font-weight: 300;
    font-size: 16px;
    line-height: 24px;
    letter-spacing: .2px;
    cursor: pointer;
    text-decoration: none;
    padding: 11px 16px;
    border-radius: 4px;
    margin: 0 12px 12px 0;
`;

export const ButtonPrimary = styled(Button)`
    background-color: #00ade6;
    color: #fff;
    border-color: #00ade6;
`;

export const ButtonSecondary = styled(Button)`
    background-color: #fff;
    color: #00ade6;
    border-color: #00ade6;
`;

will give me default buttons when consumed, only at build time they are correct.

maapteh avatar Oct 21 '20 07:10 maapteh

Same problem here

Suniron avatar Oct 21 '20 08:10 Suniron

update: I upgraded styled-components to ^5.2.0 and this issue has been resolved, and upon digging it turns out this was fixed in a recent release of SC:

  • original issue: https://github.com/styled-components/styled-components/issues/2074
  • pull fixing the issue: https://github.com/styled-components/styled-components/pull/3239

sreetamdas avatar Oct 21 '20 11:10 sreetamdas

@Suniron @maapteh @andrewgies17 perhaps this^ resolves the issue for you as well?

sreetamdas avatar Oct 21 '20 11:10 sreetamdas

I will look, nope im using 5.2.0 but have totally unstylled buttons, only in prod build they are back again. After the update to document it is now working https://medium.com/swlh/server-side-rendering-styled-components-with-nextjs-1db1353e915e

maapteh avatar Oct 21 '20 11:10 maapteh

Same here! using styled-components 5.3.0 and next version: 12.1.5. Hot reload not working with a wrapped SC

seanpaulharsevoort avatar May 18 '22 12:05 seanpaulharsevoort

For me fast refresh is working except the wrapped styled component is imported from another file. Upon saving, fast refresh updates the styles in the browser, but the changes are not reflected until I change something else (e.g. comment) or do a refresh.

My config looks like this:

"dependencies": {
    "next": "^12.2.5",
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "sharp": "^0.30.1",
    "styled-components": "^5.3.5"
  }
module.exports = {
  compiler: {
    styledComponents: true,
  }
};

DPangerl avatar Aug 26 '22 12:08 DPangerl

The same here. If I refresh the page (F5) the wrapped component broke and then I need to change something inside the wrapped component.

mayercordeiro avatar Oct 27 '22 14:10 mayercordeiro

Same here as reported by @DPangerl: next@^13.1.1, styled-components@^5.3.6

As a temporary workaround I am using next-remote-watch

Also, see this SO issue if using next-remote-watch.

ramblehead avatar Jan 26 '23 14:01 ramblehead