styled-components / styled-components

Visual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress 💅
https://styled-components.com
MIT License
40.11k stars 2.48k forks source link

Dynamic styles applied when it shouldn't be #4240

Closed arackaf closed 4 months ago

arackaf commented 4 months ago

Environment

npx envinfo --system --binaries --npmPackages styled-components,babel-plugin-styled-components --markdown

System:

Reproduction

Full Repo:

https://github.com/arackaf/styled-components-repro/blob/main/src/pages/index.tsx

import { useEffect, useRef, useState } from "react";
import styled, { css } from "styled-components";

type Props = {
  $active?: boolean;
};

const red = css`
  &,
  &:hover {
    color: red;
  }
`;

const Junk = styled.div<Props>`
  ${(props) => (props.$active ? red : null)}
`;

export default function Home() {
  const [active, setActive] = useState(false);

  const strictModeCrap = useRef(true);
  useEffect(() => {
    strictModeCrap.current &&
      setInterval(() => {
        setActive((val) => (val ? false : true));
      }, 2000);
    strictModeCrap.current = false;
  }, []);

  return (
    <main>
      <Junk $active={active}>Hello World - should be read {active ? "Yes" : "No"}</Junk>
    </main>
  );
}

Once the $active prop is ever true, the colors stick. This is probably closely related to

https://github.com/styled-components/styled-components/issues/4194

since removing the & removes the bug.

Steps to reproduce

See above

Expected Behavior

$active prop should be respected

Actual Behavior

It is not

quantizor commented 4 months ago

This is likely the same bug as https://github.com/styled-components/styled-components/issues/4224 which originates in stylis v4 (issue opened there.) unrelated, see followup comment

quantizor commented 4 months ago

Ok, after looking at this a bit more deeply I think there's a misunderstanding at play. https://styled-components.com/docs/basics#pseudoelements-pseudoselectors-and-nesting

A single & refers to the static class of the styled component, meaning any instance of that component. Combining & with a selector, e.g. &:hover creates a prop-instance style that only applies when the related prop is applied. This prop-instance style can also be triggered without a selector by doubling it up && as a "precedence boost". I will admit this is confusing.

The only styles that get unmounted after injection are global styles, that's why the & style persists after it's first mounted. The reason for this is to avoid having to reference count every single styled component...

quantizor commented 4 months ago

Hmm although looking at v5 behavior we didn't target the static class when using & which is why this worked previously. Maybe we need to undo that change.

arackaf commented 4 months ago

@quantizor does your fix also solve

https://github.com/styled-components/styled-components/issues/4194

?

quantizor commented 4 months ago

Yup!