styled-components: unknown prop の警告を解消する2つの方法
前回の記事 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 要素に 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-components
の v5.1
から追加された機能です。transient
は「一時的な」という意味ですね。
以下の公式ドキュメントやブログに transient props について詳しく書かれていますので、興味のある方はご覧ください。
- Transient props | styled-components: API Reference
- Transient Props in styled-components | by Jake McCambley | Medium
例えば、以下のようなコードでは、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
属性が渡されなくなりました。
ちなみに、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-components
の withConfig
メソッドを使って設定します。
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 内で使えなくなってしまいますので、代わりに Copy
の align
とは別名の textAlign
という名前にして、スタイルが正しく適用されるかつ、textAlign
を DOM 要素には渡さないという設定になっています。
このやり方でも DOM に渡されていないことが確認できます。
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
のことまで書かれた記事はあまりなかったので書いてみました。
この記事で触れた主なポイントをリストアップします:
-
styled-components
の unknown prop の警告・エラーについて - この警告の原因:Props が DOM 要素に属性として渡されてしまうこと
- エラー解決方法1:transient props の使用 -
$
プレフィックスを Props に付ける方法 - エラー解決方法2:
shouldForwardProp
の使用 - 特定の Props をDOM要素に渡さない設定方法 - それぞれの方法に関するサンプルコードとその適用例
styled-components の unknown props に関する警告やエラーが出たら、この記事を参考していただけると幸いです☺️
Discussion