Wrapped styled-components don't update on Fast Refresh of imported modules
See original GitHub issueBug 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 andindex.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 includesindex.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.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:8
- Comments:9 (2 by maintainers)
Top GitHub Comments
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:
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