👻

styled-components: unknown prop の警告を解消する2つの方法

2023/08/15に公開

前回の記事 Next.js を v3 から v13 に一気にアップデートした話では書けませんでしたが、styled-components をアップデートしたタイミングで、以下のような warning や error が大量に出ました。

styled-components: it looks like an unknown prop "align" is being sent through to the DOM, which will likely trigger a React console error. If you would like automatic filtering of unknown props, you can opt-into that behavior via `<StyleSheetManager shouldForwardProp={...}>` (connect an API like `@emotion/is-prop-valid`) or consider using transient props (`$` prefix for automatic filtering.)

これらの警告は styled-component で使っている align という Props が DOM 要素に属性として渡されてしまっていて、それが React コンソールエラーを発生させてしまう可能性がある、ということを警告しています。

実際にコンパイルされた DOM を Developer tool で見てみると、確かに align 属性が DOM に渡されてしまっていますね。

DOM に align 属性が渡されているコードが入った Developer tool のスクリーンショット

また、以下のようなエラーも同じように DOM 要素に Props が渡ってしまっているのが根本の原因です。

Warning: React does not recognize the `textAlign` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `textalign` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
Warning: Received `false` for a non-boolean attribute `active`.

If you want to write it to the DOM, pass a string instead: active="false" or active={value.toString()}.

If you used to conditionally omit it with active={condition && value}, pass active={condition ? value : undefined} instead.

これらを解消するためには、警告メッセージにも書かれてあるように shouldForwardProp を使うか、transient props を使う必要があります。

エラー解決方法

まずは簡単に解決できる transient props を紹介します。

transient props

メッセージの通り、$ プレフィックスを Props につければ、この Props は自動的に DOM 要素には渡されず、その styled component のみで使用できます。これを transient props と言います。

こちらは styled-componentsv5.1 から追加された機能です。transient は「一時的な」という意味ですね。

以下の公式ドキュメントやブログに transient props について詳しく書かれていますので、興味のある方はご覧ください。

例えば、以下のようなコードでは、unknown prop の警告が出てしまいます。

const StyledCopy = styled.p`
  text-align: ${({ align }) => `${align}`};
`;

const Copy = ({ align = 'center', children, ...props }) => (
  <StyledCopy {...props} align={align}>
    {children}
  </StyledCopy>
);

以下のように align$align にすることで解消します。

const StyledCopy = styled.p`
  text-align: ${({ $align }) => `${$align}`};
`;

const Copy = ({ align = 'center', children, ...props }) => (
  <StyledCopy {...props} $align={align}>
    {children}
  </StyledCopy>
);

こうすることで DOM 要素に align 属性が渡されなくなりました。

DOM に align 属性が渡されていないコードの Developer tool のスクリーンショット

ちなみに、TypeScript の場合は以下のように書いてください。

import React from "react";
import styled from "styled-components";

type Align = "left" | "center" | "right";

interface Props {
  align?: Align;
  children?: React.ReactNode;
}

const StyledCopy = styled.p<{ $align?: Align }>`
  text-align: ${({ $align }) => `${$align}`};
`;

const Copy: React.FC<Props> = ({
  align = "center",
  children,
  ...props
}) => (
  <StyledCopy {...props} $align={align}>
    {children}
  </StyledCopy>
);

export default Copy;

基本的にこのやり方で対応できますが、念の為に shouldForwardProp を使うやり方も説明します。

shouldForwardProp

shouldForwardProp も同じく v5.1 から追加された機能で、簡単に言うと同じく特定の Props を DOM 要素に渡さないために使います。

正直僕もそこまで使ったことはないので、詳しいことはまだあまり理解していないのですが、僕の現在の理解では transient props で対応できないような複雑なものは shouldForwardProp で対応する、といった感じでしょうか。公式ドキュメントにも "This is a more dynamic, granular filtering mechanism than transient props." と書かれています。例えば、複数の props を動的にフィルタリングしたい場合や、特定の条件下で特定の props だけをフィルタリングしたい場合とかですかね。

先ほどの例を shouldForwardProp で書いてみました。styled-componentswithConfig メソッドを使って設定します。

const StyledCopy = styled.p.withConfig({
  shouldForwardProp: prop => !['textAlign'].includes(prop),
})`
  text-align: ${props => `${props.textAlign}`};
`;

const Copy = ({ align = 'center', children, ...props }) => (
  <StyledCopy {...props} textAlign={align}>
    {children}
  </StyledCopy>
);

StyledCopy の Props を align -> textAlign にしています。これは align にしてしまうと align 自体がこの styled component 内で使えなくなってしまいますので、代わりに Copyalign とは別名の textAlign という名前にして、スタイルが正しく適用されるかつ、textAlign を DOM 要素には渡さないという設定になっています。

このやり方でも DOM に渡されていないことが確認できます。

DOM に align 属性が渡されていないコードの Developer tool のスクリーンショット

TypeScript で書くと以下のようなコードになります。

import React from "react";
import styled from "styled-components";

type Align = "left" | "center" | "right";

interface Props {
  align?: Align;
  children?: React.ReactNode;
}

const StyledCopy = styled.p.withConfig({
  shouldForwardProp: (prop) => !["textAlign"].includes(prop),
})<{ textAlign?: Align }>`
  text-align: ${(props) => `${props.textAlign}`};
`;

const Copy: React.FC<Props> = ({
  align = "center",
  children,
  ...props
}) => (
  <StyledCopy {...props} textAlign={align}>
    {children}
  </StyledCopy>
);

export default Copy;

どちらを使うべきか?

正直これくらいのコードであれば $ プレフィックスをつけるだけの transient props を使う方が絶対楽ですね。まずは transient props でやってみて、それで対応できないような複雑な場合はshouldForwardProp を使えばいいと思います。

ひとまず transient props と shouldForwardProp という、このエラーを解消する二つの方法があるということを知っておくだけでも、どこかで役に立つかもしれません。

まとめ

styled-components の transient props に関する記事は結構ありましたが、shouldForwardProp のことまで書かれた記事はあまりなかったので書いてみました。

この記事で触れた主なポイントをリストアップします:

  1. styled-components の unknown prop の警告・エラーについて
  2. この警告の原因:Props が DOM 要素に属性として渡されてしまうこと
  3. エラー解決方法1:transient props の使用 - $ プレフィックスを Props に付ける方法
  4. エラー解決方法2:shouldForwardProp の使用 - 特定の Props をDOM要素に渡さない設定方法
  5. それぞれの方法に関するサンプルコードとその適用例

styled-components の unknown props に関する警告やエラーが出たら、この記事を参考していただけると幸いです☺️

Discussion