styled-components
를 활용하여 공통 UI Component
를 개발하던 중 다음과 같은 콘솔 에러에 대한 해결 과정을 다룬 아티클입니다.
Warning: React does not recognize the
x
prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercasex
instead. If you accidentally passed it from a parent component, remove it from the DOM element.
글이나 문단을 표현할때, 매번 파일에 스타일을 정의하는 것이 불편하여 공통 컴포넌트를 제작했습니다.
import type { PropsWithChildren, HTMLAttributes } from 'react'
import styled, { css } from 'styled-components'
import { Property } from 'csstype'
import { ColorsTypes } from '@/utils/global-colors'
interface TextProps extends HTMLAttributes<HTMLParagraphElement>{
size?: number
bold?: boolean
color?: keyof ColorsTypes | Property.Color
maxLines?: number
}
const TextBase = styled.p<TextProps>`
box-sizing: border-box;
${({
size,
bold,
color,
}) => css`
font-size: ${size}px;
font-weight: ${bold ? 700 : 400};
color: ${color};
`}
${maxLines &&
css`
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: ${maxLines};
text-overflow: ellipsis;
overflow: hidden;
white-space: pre-line;
`};
`
export function Text({
size,
bold,
color,
maxLines,
children,
...props
}: PropsWithChildren<TextProps>) {
return (
<TextBase
size={size}
bold={bold}
color={color}
maxLines={maxLines}
{...props}
>
{children}
</TextBase>
)
}
코드 측면에서 봤을 때에는 큰 이슈는 없지만, Text Component
를 렌더링하게 되면 Console 창에 다음과 같은 오류 메시지가 노출됩니다.
Warning: React does not recognize the
maxLines
prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercasemaxLines
instead. If you accidentally passed it from a parent component, remove it from the DOM element.
메시지의 원인은 React
가 인식할 수 있는 props
가 아니면 custom props
로 인식을 하고, custom props
는 소문자로만 작성을 해야하는 Propery 검증 로직이 있기 때문입니다.
이를 해결하기 위해서, styled-components
는 스타일 prop
이 기본 React
노드로 전달되거나 DOM 요소로 렌더링되는 것을 방지하도록 $기능을 제공하고 있습니다.
이를 기반으로 수정하면 다음과 같은 코드가 되며, 콘솔 창에서 에러 메시지가 노출되지 않는 걸 확인할 수 있습니다.
import type { PropsWithChildren, HTMLAttributes } from 'react'
import styled, { css } from 'styled-components'
import { Property } from 'csstype'
import { ColorsTypes } from '@/utils/global-colors'
interface TextProps extends HTMLAttributes<HTMLParagraphElement>{
size?: number
bold?: boolean
color?: keyof ColorsTypes | Property.Color
maxLines?: number
}
interface TextStyleProps {
$size?: number
$bold?: boolean
$color?: keyof ColorsTypes | Property.Color
$maxLines?: number
}
const TextBase = styled.p<TextStyleProps>`
box-sizing: border-box;
${({
$size,
$bold,
$color,
}) => css`
font-size: ${$size}px;
font-weight: ${$bold ? 700 : 400};
color: ${$color};
`}
${$maxLines &&
css`
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: ${$maxLines};
text-overflow: ellipsis;
overflow: hidden;
white-space: pre-line;
`};
`
export function Text({
size = 13,
bold = false,
color = 'gray900',
maxLines,
children,
...props
}: PropsWithChildren<TextProps>) {
return (
<TextBase
$size={size}
$bold={bold}
$color={color}
$maxLines={maxLines}
{...props}
>
{children}
</TextBase>
)
}
이슈는 없지만 타입 부분을 보면 $
표시를 제외하고 형식이 동일합니다.
interface TextProps extends HTMLAttributes<HTMLParagraphElement>{
size?: number
bold?: boolean
color?: keyof ColorsTypes | Property.Color
maxLines?: number
}
interface TextStyleProps {
$size?: number
$bold?: boolean
$color?: keyof ColorsTypes | Property.Color
$maxLines?: number
}
이러한 구조는 props
를 추가할 때마다 타입을 2벌 관리해야하는 불편함이 있습니다.
자동으로 $
를 붙여주는 타입을 작성하면 타입 누락을 방지할 수 있습니다.
// @/types
export type StyleProps<T> = {
[K in keyof T as `$${string & K}`]: T[K]
}
해당 타입을 적용한 최종 코드는 다음과 같습니다.
import type { PropsWithChildren, HTMLAttributes } from 'react'
import styled, { css } from 'styled-components'
import { Property } from 'csstype'
import type { ColorsTypes } from '@/utils/global-colors'
import type { StyleProps } from '@/types'
interface TextProps extends HTMLAttributes<HTMLParagraphElement>{
size?: number
bold?: boolean
color?: keyof ColorsTypes | Property.Color
maxLines?: number
}
type TextStyleProps = StyleProps<TextProps>
const TextBase = styled.p<TextStyleProps>`
box-sizing: border-box;
${({
$size,
$bold,
$color,
}) => css`
font-size: ${$size}px;
font-weight: ${$bold ? 700 : 400};
color: ${$color};
`}
${$maxLines &&
css`
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: ${$maxLines};
text-overflow: ellipsis;
overflow: hidden;
white-space: pre-line;
`};
`
export function Text({
size = 13,
bold = false,
color = 'gray900',
maxLines,
children,
...props
}: PropsWithChildren<TextProps>) {
return (
<TextBase
$size={size}
$bold={bold}
$color={color}
$maxLines={maxLines}
{...props}
>
{children}
</TextBase>
)
}
좋네여🤡