React does not recognize the `x` prop on a DOM element

Finn·2024년 2월 8일
1

문제해결

목록 보기
1/3
post-custom-banner

개요

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 lowercase x 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 lowercase maxLines 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>
  )
}
profile
소통을 좋아하는 프론트엔드 개발자 🙈
post-custom-banner

1개의 댓글

comment-user-thumbnail
2024년 9월 14일

좋네여🤡

답글 달기