Warning: Received "true" for a non-boolean attribute 해결하기

정성연·2023년 8월 15일
5

문제

Styled 컴포넌트를 사용하면서 아래와 같은 warning이 계속해서 뜨는것을 확인했다.

  • Console warning 1.

    React does not recognize the keywordType prop on a DOM element.
    If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase keywordtype instead.
    If you accidentally passed it from a parent component, remove it from the DOM element.

  • Console warning 2.

    Warning: Received true for a non-boolean attribute 속성.
    If you want to write it to the DOM, pass a string instead: 속성="true" or 속성={value.toString()}.

콘솔창에 수없이 많이뜨는 위 두 경고 문구를 보고 왜 뜨는 건지 원인 과 해결방법을 찾아보았다.

원인

React does not recognize the keywordType prop on a DOM element.

적힌 그대로 React DOM이 알지 못하는 키워드여서 나오는 warning 이다.

Warning: Received true for a non-boolean attribute attribute.

HTML attribute로 boolean 값이 들어갔을떄 나는 warning 이다.

위 에러들은 HTML의 표준 속성에 대해 알고 넘어가야 한다.
기본적으로 HTML 시멘틱 태그는 width, height 등등 다양한 속성을 가지고 있다.

이러한 속성을 HTML 표준 속성이라고 부른다.

이 표준속성 리스트 외 styled를 위해, 다른 custom 속성을 전달하게 된다면, 위와 같은 에러들이 난다.

Styled 컴포넌트는 기본적으로 모든 custom 속성을 HTML attribute로 전달 한다.

const Link = props => (
  <a {...props} className={props.className}>
    {props.text}
  </a>
)


const StyledComp = styled(Link)`
  color: ${props => (props.red ? 'red' : 'blue')};
`


<StyledComp text="Click" href="https://www.styled-components.com/" red />

만약 다음과 같이 red라는 props를 Styled로 넘기게 된다면

<a text="Click" href="https://www.styled-components.com/" red="true" class="[generated class]">Click</a>

HTML DOM 에는 위외 같이 red가 있는 상태로 렌더링 될 것이다.

해결 방법

  1. Styled Component - transient props 5.1 version 이상

Styled는 custom props들이 React props 로 전달 되거나, DOM 요소로 렌더링 되지 않도록 기호($)를 prefix로 붙이는 기능을 제공한다.

render(
  <Comp $draggable="red" draggable="true">
    Drag me!
  </Comp>
);

const Comp = styled.div`
  color: ${props =>
    props.$draggable || 'black'};
`;

위와 같이 작성하면 $draggable 는 Styled로 전달되지만 HTML attribute로는 추가되지 않는다.

  1. Styled Component - shouldForwardProp 5.1 version 이상

만약 $를 붙이는 방식이 마음에 들지 않는다면 사용 할 수 있는 방법이다.

두번째 방법으로는 Styled의 shouldForwardProp 기능을 사용하는 것이다.

const Comp = styled('div').withConfig({
  shouldForwardProp: (prop) =>
      !['hidden'].includes(prop),
}).attrs({ className: 'foo' })`
  color: red;
  &.foo {
    text-decoration: underline;
  }
`;

위 와 같이 styled의 Config 를 통해 shouldForwardProp를 콜백 함수를 받을 수 있다.

콜백함수의 첫번쨰 인자로 prop에는 propName 들이 들어오게 되고, true를 리턴한다면, HTML attribute또는 React Props 로 넘길 수 있고, false 를 리턴한다면 Styled 에서만 사용가능한 custom Props 로 만들 수 있다.

위의 transient props($)의 기능도 내부적으로 보면
shouldForwardProp이 global Config로서 적용되어 있는 로직이다.

shouldForwardProp: (propName) => {
	return !propName.startWith('$')
}

해결시 장점

위의 문제를 해결한다면 다음과 같은 이점이 있다.

  • Styled-components 를 사용할 때 불필요한 속성이 DOM 에 전달되는 것을 방지 할 수 있다.

  • $ prefix 를 사용함으로서 React props 로 넘어가는 속성과 Styled props 로 넘어가는 속성을 명시적으로 구분할 수 있다.

조금 더 우아하게 해결하기

위의 $를 붙이는 transient props 방식이나 shouldForwardProsps 방식을 이용해 warning 을 해결하는 방식 모두 문제를 해결하는 방법이지만,

Styled props 변수명을 $로 모두 수정하거나, 각각의 Styled 컴포넌트 Config에 shouldForwardProps 를 넘기고 싶지 않을 경우 아래와 같은 wrapper 함수를 만드는 방식으로 문제를 해결 할 수 있다.

// styled.jsx file

import styled  from 'styled-components';

const styledWrapper = (tag) => {
  return styled(tag).withConfig({
  shouldForwardProp: (prop) =>
      isPropValid(prop)
}

export const styledWrapper

@emotion/is-prop-valid 를 사용하면 HTML의 표준속성을 자동적으로 걸려줄 수 있다.

import isPropValid from '@emotion/is-prop-valid'

isPropValid('href') // true

isPropValid('someRandomProp') // false

하지만 위와 같이 사용하게 되면 기존 태그들은 문제 없이 동작하지만, 아래와 같이 Styled를 통해서 컴포넌트를 override 하는 경우는 Child Component로 넘겨야할 props 조차도 전달이 막히는 문제가 발생한다.

const Comp = styledWrapper(OtherComp)``

이는 조건문을 걸어 해결 할 수 있지만 override 된 Styled는 따로 shouldProps를 적용해 주어야 한다.

// styled.ts file

import _styled from 'styled-components';

const styled = ((component: any, config: any) => {
  if(typeof tag === 'string') {
    	config = { 
    		shouldForwardProp: (prop: string) => 
		    isPropValid(prop),
          	...config 
		};
  }
  return _styled(component, config);
}) as (typeof _styled);

const tags = 'a|abbr|address|area|article|aside|audio|b|base|bdi|bdo|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|data|datalist|dd|del|details|dfn|dialog|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|main|map|mark|marquee|menu|menuitem|meta|meter|nav|noscript|object|ol|optgroup|option|output|p|param|picture|pre|progress|q|rp|rt|ruby|s|samp|script|section|select|small|source|span|strong|style|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|track|u|ul|var|video|wbr|circle|clipPath|defs|ellipse|foreignObject|g|image|line|linearGradient|mask|path|pattern|polygon|polyline|radialGradient|rect|stop|svg|text|tspan'.split('|');
for(const tag of tags){
  (styled as any)[tag] = styled(tag as any);
}

export default styled;
참고
profile
개발자

1개의 댓글

comment-user-thumbnail
2023년 8월 15일

감사합니다. 이런 정보를 나눠주셔서 좋아요.

답글 달기