TECH

Storybook에서 UseState 사용하기

셈인 2024. 5. 25. 13:18

개요

프로젝트를 진행하며 커스텀 체크박스 컴포넌트를 제작했습니다.
이 컴포넌트는 외부에서 useState를 사용하여 상태를 관리하고, 그 값을 value로 바인딩하는 방식으로 설계되었습니다.
그러나 Storybook에서 테스트할 때는 value 값을 외부에서 변경해 줄 수 없었기 때문에, 클릭 이벤트에 의해 UI가 변경되지 않는 문제가 발생했습니다.

 

Storybook에서 체크박스의 상태 변화를 시각적으로 확인할 수 있기를 희망했고, useState를 Storybook 내에서 사용하는 방법을 찾아보았습니다. 그리고 해결 방법을 이 글을 통해 서술하겠습니다.

 

프로젝트 환경

Next 14.1.4 
Storybook 8.0.6

 

checkbox.tsx

먼저, 내가 작성한 Checkbox 컴포넌트의 코드는 아래와 같습니다.


상위 컴포넌트에서 onChange를 통해 checked를 변경해주지 않으면 스스로 값을 변경할 수 없는 컴포넌트입니다.
즉, Checkbox 컴포넌트는 상위 컴포넌트에 종속되는 컴포넌트입니다.

// checkbox.tsx
import React from 'react';
import clsx from 'clsx';
import styles from './checkbox.module.scss';

interface CheckboxPros<T> {
  checked: boolean;
  label: string;
  onChange: (value: T) => void;
  disabled?: boolean;
  value?: T;
}

const Checkbox = <T,>({
  disabled = false,
  checked,
  label,
  onChange,
  value,
}: CheckboxPros<T>) => {
  function toggleChecked() {
    if (!value) {
      onChange(!checked as T);
      return;
    }

    const updatedValue = (checked ? null : value) as T;
    onChange(updatedValue);
  }

  return (
    <label className={clsx(
      styles.checkbox,
      {
        [styles.disabled]: disabled,
      }
    )}>
      <input
        type="checkbox"
        className="hidden"
        checked={checked}
        onChange={toggleChecked}
        disabled={disabled}
      />
      <div className={styles.icon}>
        <i className="material-symbols-rounded">check</i>
      </div>
      <span>{ label }</span>
    </label>
  );
};

export default Checkbox;

 

UseState를 활용하기

CheckboxWithHooks와 같이 컴포넌트를 새로 정의해주고, 해당 컴포넌트를 render를 이용해 스토리를 선언해줍니다.
tsx 문법을 사용해야 하기에, 파일의 확장자도 tsx로 변경이 필요합니다.

// checkbox.stories.tsx
const CheckboxWithHooks = ({
  checked,
  disabled,
  label,
  value,
}: any) => {
  const [checkedState, setCheckedState] = useState(checked);

  const onChange = (value: boolean) => {
    setCheckedState(value);
  };

  return <Checkbox
    checked={checkedState}
    disabled={disabled}
    label={label}
    onChange={onChange}
    value={value}
  />;
};

export const Unchecked: Story = {
  args: {
    checked: false,
  },
  render: (args) => <CheckboxWithHooks {...args} />,
};

 

마무리

useState를 어떻게 사용해야 할지 고민하고 찾아본 것에 비해 생각보다 간단하게 구현할 수 있었다.
아직까지는 storybook8에 대한 정보와 다른 분들의 코드 예시가 부족하여 해결 방법을 찾는데 시간이 꽤 걸린 것 같다.

하지만, 항상 정답은 공식 문서에 나와 있으므로 괜히 헤매지 말고 공식 문서를 1순위로 보는 걸 권장드립니다.
엄청 친절하진 않아도, 필요한 정보는 전부 있더라구요👍