I have this in some parts of the code:
import React from 'react'
import styled from 'styled-components'
export type BoxType = React.ReactElement<typeof Box>
type JustifyType = 'between' | 'center'
type ScrollType = 'auto' | 'scroll'
type PropsType = {
direction?: 'horizontal' | 'vertical'
flex?: number | string
width?: number | string
minWidth?: number | string
height?: number | string
minHeight?: number | string
fill?: string
justify?: JustifyType
padding?: number | string
paddingTop?: string | number
paddingRight?: string | number
paddingBottom?: string | number
paddingLeft?: string | number
scrollX?: ScrollType
scrollY?: ScrollType
}
const JUSTIFY: Record<JustifyType, string> = {
between: 'space-between',
center: 'center',
}
const Box = styled.div<PropsType>`
display: flex;
${({ scrollX }) => scrollX && `overflow-x: ${scrollX};`}
${({ scrollY }) => scrollY && `overflow-y: ${scrollY};`}
${({ padding }) =>
padding &&
`padding: ${typeof padding === 'number' ? `${padding}px` : padding};`}
${({ width }) =>
width && `width: ${typeof width === 'number' ? `${width}px` : width};`}
${({ minWidth }) =>
minWidth &&
`min-width: ${typeof minWidth === 'number' ? `${minWidth}px` : minWidth};`}
${({ height }) =>
height && `height: ${typeof height === 'number' ? `${height}px` : height};`}
${({ minHeight }) =>
minHeight &&
`min-height: ${
typeof minHeight === 'number' ? `${minHeight}px` : minHeight
};`}
${({ flex }) => flex && `flex: ${flex};`}
${({ fill }) => fill && `background-color: ${fill};`}
${({ direction }) =>
direction &&
`flex-direction: ${direction === 'horizontal' ? 'row' : 'column'};`}
${({ justify }) => justify && `justify-content: ${JUSTIFY[justify]};`}
${({ paddingTop }) =>
paddingTop &&
`padding-top: ${
typeof paddingTop === 'number' ? `${paddingTop}px` : paddingTop
};`}
${({ paddingRight }) =>
paddingRight &&
`padding-right: ${
typeof paddingRight === 'number' ? `${paddingRight}px` : paddingRight
};`}
${({ paddingBottom }) =>
paddingBottom &&
`padding-bottom: ${
typeof paddingBottom === 'number' ? `${paddingBottom}px` : paddingBottom
};`}
${({ paddingLeft }) =>
paddingLeft &&
`padding-left: ${
typeof paddingLeft === 'number' ? `${paddingLeft}px` : paddingLeft
};`}
`
export default Box
And then I have this:
export default function Page({ url, tab, note, tags }: PagePropsType) {
const [output, setOutput] = useState('')
const handleChange = (e: React.FormEvent<HTMLTextAreaElement>): void => {
if (e.target instanceof HTMLTextAreaElement) {
setOutput(clearNiqqud(e.target.value ?? ''))
}
}
return (
<Content url={url} tab={tab} note={note} tags={tags}>
<Box padding={24} width="100%">
<Type.H1 align="center">{tab}</Type.H1>
</Box>
{/* נִקּוּד */}
<Box fill={COLOR.white} paddingTop={24} paddingBottom={24}>
<Type.Text
paddingLeft={24}
paddingRight={24}
align="right"
font="hebrew"
size={40}
rows={4}
onChange={handleChange}
/>
</Box>
<Box padding={24}>
<Type.P preserveNewlines font="hebrew" size={40} align="right">
{output}
</Type.P>
</Box>
</Content>
)
}
The relevant part is the fill
attribute, and other attributes, which show up in the DOM:
Is this normal behavior? Or how do I remove it from the DOM? I am using Next.js and have the default next.config.js
, with this tsconfig.json
:
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"types": ["node"],
"baseUrl": "."
},
"include": ["next-env.d.ts", "index.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"],
"ts-node": {
"compilerOptions": {
"module": "commonjs"
}
}
}
CodePudding user response:
Indeed we normally expect that styled-components
is smart enough to not pass extra props to styled base HTML tags (like div
in this case):
If the styled target is a simple element (e.g.
styled.div
), [...] styled-components [is] smart enough to filter non-standard attributes automatically for you.
Unfortunately, the "smart enough" is not enough in this case: fill
attribute is standard for SVG elements, hence styled-components
lets it through...
A very simple workaround is to use a transient prop instead. It consists in prefixing the prop with a dollar sign:
If you want to prevent props meant to be consumed by styled components from being passed to the underlying React node or rendered to the DOM element, you can prefix the prop name with a dollar sign (
$
), turning it into a transient prop.
const Box2 = styled.div<{ $fill?: string; }>`
${({ $fill }) => $fill && `background-color: ${$fill};`}
`;
<Box2 $fill="yellow">Content...</Box2>
Unfortunately, this means slightly changing your internal API.
Another workaround consists in using the shouldForwardProp
configuration. With this, we can precisely specify what should be passed through or not:
A prop that fails the test isn't passed down to underlying components, just like a transient prop.
const Box3 = styled.div.withConfig({
shouldForwardProp(prop, isValidProp) {
return (prop as string) !== "fill" && isValidProp(prop);
}
})<{ fill?: string; }>`
${({ fill }) => fill && `background-color: ${fill};`}
`;
<Box3 fill="yellow">Content...</Box3>
Demo: https://codesandbox.io/s/wispy-silence-mp54zv?file=/src/App.tsx
Some more explanation on the styled-components
built-in "smart" filter: as implied above, as soon as the prop is a standard attribute for any HTML standard element (like fill
for SVG in this case), it is white listed, and passed through.
This is because styled-components
actually relies on @emotion/is-prop-valid
, which implements the filter as a single common white list for simplicity, instead of having a list per HTML standard tag.
We can find the fill
attribute white listed here.