0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

styled-componentsは拡張元のコンポーネントに`theme`という名前のpropを渡さない

Last updated at Posted at 2023-10-21

概要

styled-componentsは拡張元のコンポーネントにthemeという名前のpropを渡さないようです。
そのため、拡張元のコンポーネントにthemeという名前のpropを渡したい場合は少し工夫をする必要があります。

以下の説明で使用するサンプルコードは以下のURLに載せてあります。
https://github.com/ishioka0222/styled-components-theme-prop

経緯

Ant DesignというReact用のUIフレームワークに、themeという名前のpropの値に応じて見た目を変更できるコンポーネントがあるのですが、そのコンポーネントをstyled-componentsで拡張したところ、themeという名前のpropの値だけが反映されなくなりました。
調べたところ、styled-componentsの実装に原因があることが分かったのですが、バグというよりは想定された挙動のようにも見えました。
備忘録として、ここに事象とその対処法について書き残しておきます。

動作を確認した環境

  • node.js: v18.18.0
  • react: v18.2.0
  • react-dom: v18.2.0
  • styled-components: v6.1.0

事象について

例えば、外部のライブラリに、themeというpropの値に応じてfilledoutlinedの2つの見た目を選べるようなThirdPartyComponentというコンポーネントがあるとします。

type ThirdPartyTheme = "filled" | "outlined";

interface ThirdPartyComponentProps {
  className?: string;
  theme: ThirdPartyTheme;
}

const ThirdPartyComponent: React.FC<
  PropsWithChildren<ThirdPartyComponentProps>
> = (props) => {
  return (
    <div
      className={props.className}
      style={{
        padding: "0.5rem",
        margin: "0.5rem 0",
        border: "1px solid dodgerblue",
        backgroundColor:
          props.theme === "filled" ? "dodgerblue" : "transparent",
        color: props.theme === "filled" ? "whitesmoke" : "dodgerblue",
      }}
    >
      {`theme: ${props.theme}`}
      <hr />
      {props.children}
    </div>
  );
};

function App() {
  return (
    <>
      <ThirdPartyComponent theme={"filled"}>
        ThirdPartyComponent's children...
      </ThirdPartyComponent>
      <ThirdPartyComponent theme={"outlined"}>
        ThirdPartyComponent's children...
      </ThirdPartyComponent>
    </>
  );
}

このコンポーネントのthemeの値にfilledoutlinedを指定すると、それぞれ以下のような見た目になります。

次に、このコンポーネントに影を付けようと思い、以下のようにstyled-componentsで拡張したとします。

const StyledThirdPartyComponent = styled(ThirdPartyComponent)`
  box-shadow: 0 8px 8px 0px #333;
`;

function App() {
  return (
    <>
      <StyledThirdPartyComponent theme={"filled"}>
        StyledThirdPartyComponent's children...
      </StyledThirdPartyComponent>
      <StyledThirdPartyComponent theme={"outlined"}>
        StyledThirdPartyComponent's children...
      </StyledThirdPartyComponent>
    </>
  );
}

すると、ThirdPartyComponentが受け取ったthemeの値はundefinedになってしまいます。

こうなってしまう理由はよく分からないのですが、styled-componentsのソースコードを見ると、以下の部分でthemeというpropを意図的に読み飛ばしているようです

if (context[key] === undefined) {
      // Omit undefined values from props passed to wrapped element.
      // This enables using .attrs() to remove props, for example.
    } else if (key[0] === '$' || key === 'as' || key === 'theme') {
      // Omit transient props and execution props.
    } else if (key === 'forwardedAs') {
      propsForElement.as = context.forwardedAs;

styled-componentsにもtheme機能があるため、それとの兼ね合いでしょうか・・・。

対処法について

styled-componentsのGitHub Issuesでもこの件は報告されていますが、themeというpropの名前を変更して回避するしかないようです。

具体的にはthemeと被らないようなprop名を適当に用意します。ここでは仮に_themeとします1

そして、styled-componentsでスタイルを拡張するときに、_themeで受け取った値を、themeという名前で拡張元のコンポーネントに渡すようにします。

type StyledThirdPartyComponentProps = Omit<
  ThirdPartyComponentProps,
  "theme"
> & { _theme: ThirdPartyTheme };

const StyledThirdPartyComponent = styled(
  ({ _theme, ...props }: PropsWithChildren<StyledThirdPartyComponentProps>) => (
    <ThirdPartyComponent {...props} theme={_theme} />
  )
)`
  box-shadow: 0 8px 8px 0px #333;
`;

function App() {
  return (
    <>
      <StyledThirdPartyComponent _theme={"filled"}>
        StyledThirdPartyComponent's children...
      </StyledThirdPartyComponent>
      <StyledThirdPartyComponent _theme={"outlined"}>
        StyledThirdPartyComponent's children...
      </StyledThirdPartyComponent>
    </>
  );
}

こうすれば、以下の画像のように_themeの値が適用され、影も付けることができます。

上の説明で使用したサンプルコードは以下のURLに載せてあります。
https://github.com/ishioka0222/styled-components-theme-prop

  1. ここの変数名は基本的にはなんでも構いませんが、$から始まるpropはstyled-componentsでtransient propと呼ばれ、拡張元のコンポーネントに渡されないため、一文字目には$以外の文字を使用する必要があります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?