I'm trying to fix a piece of TSX code that shares a prop interface between a React component and a styled <div>
, because the styled <div>
generates warnings like this:
Warning: React does not recognize the
aA
prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercaseaa
instead. If you accidentally passed it from a parent component, remove it from the DOM element.
The code looks something like this:
// Comp.tsx
interface Props {
aA: boolean;
bB: string;
cC: '1' | '2';
}
const StyledDiv = styled.div<Props>`
background: ${({ aA }) => aA ? 'black' | 'white'};
// ...
`;
const Comp = ({ aA, bB, cC}: Props) => {
if (aA) {
// do something
} else {
// do something else
}
return <StyledDiv aA={aA} bB={bB} cC={cC}>{/*...*/}</StyledDiv>;
};
export default Comp;
// StyledComp.tsx
const StyledComp = styled(Comp)<{ dD: string, eE: number }>`
// ...
`;
export default StyledComp;
What's the optimal way to go about this? I'm thinking of creating another interface DivProps
that mirrors Props
, but with transient props:
interface DivProps {
$aA: boolean;
$bB: string;
$cC: '1' | '2';
}
const StyledDiv = styled.div<DivProps>`
background: ${({ $aA }) => $aA ? 'black' | 'white'};
// ...
`;
I don't like the idea very much because I'd have to update both Props
and DivProps
if there's any changes in the future. But if this is the only solution, then please let me know as well. Thanks!
That is the use case for the shouldForwardProp
option of .withConfig()
method:
This is a more dynamic, granular filtering mechanism than transient props. It's handy in situations where multiple higher-order components are being composed together and happen to share the same prop name.
shouldForwardProp
works much like the predicate callback ofArray.filter
. A prop that fails the test isn't passed down to underlying components, just like a transient prop.
In your case:
const StyledDiv = styled.div.withConfig({
shouldForwardProp: (prop) => !["aA", "bB", "cC"].includes(prop)
})<Props>`
background: ${({ aA }) => aA ? 'black' : 'white'};
// ...
`;
A more general solution would be to setup a global shouldForwardProp
thanks to the StyleSheetManager
:
import isPropValid from '@emotion/is-prop-valid';
import { StyleSheetManager } from 'styled-components';
function MyApp() {
return (
<StyleSheetManager shouldForwardProp={shouldForwardProp}>
{/* other providers or your application's JSX */}
</StyleSheetManager>
)
}
// This implements the default behavior from styled-components v5
function shouldForwardProp(propName, target) {
if (typeof target === "string") {
// For HTML elements, forward the prop if it is a valid HTML attribute
return isPropValid(propName);
}
// For other elements, forward all props
return true;
}
See also:
If you are ready to change the API of your StyledDiv
to use the $
sign for transient props, but you just want to avoid having to manually maintain the DivProps
interface, you can automatically generate it thanks to TypeScript Mapped Types and Key Remapping:
type DivProps = {
[Key in keyof Props as `$${Key}`]: Props[Key];
};
const StyledDiv2 = styled.div<DivProps>`
background: ${({ $aA }) => ($aA ? 'black' : 'white')};
// ...
`;
const Comp = ({ aA, bB, cC }: Props) => {
// ...
return (
<>
<StyledDiv aA={aA} bB={bB} cC={cC}>
{/*...*/}
</StyledDiv>
<StyledDiv2 $aA={aA} $bB={bB} $cC={cC}>
{/*...*/}
</StyledDiv2>
</>
);
};
Demo: https://stackblitz.com/edit/react-ts-4dablowz?file=App.tsx