Skip to content

[이슈 해결] styled로 만든 컴포넌트에 boolean attribute를 제공할 때 발생하는 이슈

yangseungchan edited this page Nov 23, 2022 · 1 revision

TLDR

Emotion의 똥꼬집

예시

다음과 같이 내가 작성한 커스텀 컴포넌트를 작성하였다.

import styled from '@emotion/styled';
import { IconPlus } from '@tabler/icons';
import { useState } from 'react';

interface StyledIconPlusProps {
  opened: boolean;
}

const StyledIconPlus = styled(IconPlus, transientOptions)<StyledIconPlusProps>`
  transition: transform 0.2s ease-in-out;
  ${({ opened }) => opened && `transform: rotate(45deg);`}
`;

const Mycomponent = () => {
	const opened = useState(false);

	return(
		<StyledIconPlus size={24} opened={opened} />
	)
}

그런데 이렇게 작성할 경우 아래와 같은 에러가 발생하게 된다.

Warning: Received false for a non-boolean attribute opened. If you want to write it to the DOM, pass a string instead: opened=”false” or opened={value.toString()}. If you used to conditionally omit it with opened={condition && value}, pass opened={condition ? value : undefined} instead.

문제의 이유

  1. 기본 HTML Tag(div, svg ) 에 대한 attirubute 들은 DOM에 표시 된다.

    1. StyledIconPlus 는 기본 태그인 svg 를 추상화한 컴포넌트다.

    2. 가령 <StyledIconPlus size={24} hello="world" /> 라고 작성하면 hello 라는 attribute가 아래 처럼 실제 DOM에 기입된다.

      image

  2. 그런데 DOM attribute 에는 boolean 타입이 아니라 문자열만 들어갈 수 있다. 왜냐하면 이는 모든 DOM 요소는 HTML에 작성되어야 하기 때문이다.

따라서 이를 해결하려면 DOM 요소에 해당 attirbute 가 기입되지 않도록 설정해 주어야 한다.

해결책

1차적인 해결책

이를 위한 조치로 emotion 에서는 shouldForwardProps 라는 인터페이스를 제공한다.

const TabsStyled = styled(MuiTabs, {
  shouldForwardProp: prop =>
    isPropValid(prop) && prop.startsWith('$')
})`
   color: ${props =>
    props.$myCustomVariantThatStartsWithDollarSign ? 'hotpink' : 'turquoise'};

`

위와 같이 작성하면 $ 표시로 된 attribute 들은 DOM에서 표시하지 않겠다라는 것을 의미한다.

그런데 이걸 모든 컴포넌트를 정의할때마다 사용하는것은 매우 불편하다. 이를 간소화 하면 아래와 같이 작성할 수 있다.

2차적인 해결책

아래와 같은 유틸함수를 정의하고

import { CreateStyled } from '@emotion/styled';

const transientOptions: Parameters<CreateStyled>[1] = {
  shouldForwardProp: (propName: string) => !propName.startsWith('$'),
};

export { transientOptions };

이 유틸함수를 아래의 컴포넌트처럼 기입해주면 사용성이 조금 더 간결해진다.

interface StyledIconPlusProps {
  $opened: boolean;
}

const StyledIconPlus = styled(IconPlus, transientOptions)<StyledIconPlusProps>`
  transition: transform 0.2s ease-in-out;
  ${({ $opened }) => $opened && `transform: rotate(45deg);`}
`;

이는 $opened 라는 attribute를 DOM에는 표시하지 않도록하여 에러가 나는 것을 막아준다.

왜 이렇게 까지함?

이것도 사실 불편한데 이는 사실 emotion 의 똥꼬집(?) 때문이다.

대체제인 Styled-components 는 5.1 버전부터 저 위의 유틸함수를 정의하고 사용할 필요없이 원하는 attribute 앞에 $ 표시만 붙이면 DOM에 표시하지 않도록 하는 기능을 해주는데…

https://github.com/emotion-js/emotion/issues/2193 에서 볼 수 있듯이 수많은 사람들이 이 기능의 추가를 요청하고 있음에도 똥꼬집을 부리며 안해주고 있다.

처음으로 Emotion 의 불편함을 체감할 수 있었다….

결론

기초적인 tag 가 포함된 컴포넌트에 boolean 타입의 속성을 전달해야 한다면 2차적인 해결책 을 사용해야 한다.

Clone this wiki locally