스토리북 작성 중 발생한 순환 참조 에러 해결하기: Dropdown 컴포넌트 사례 분석
Storybook
[닫기]
🎯 이 문서를 읽고 난 후의 상태
- 스토리북의 실제 사용 사례를 알았다.
- 스토리북 작성시 마주한 순환 참조 문제를 해결하는 방법을 알았다. 또한 주의사항까지도.
- 코드를 짤 때 순환 참조 문제가 발생하지 않으려면 어떻게 해야하는 지 알았다.
😭 스토리북 사용 중 만난 에러

앞에서 탐구한 내용을 바탕으로 스토리를 작성하다가 위와 같은 문제를 만나게 되었다.
무엇이 문제일까 싶어서 트러블 슈팅을 한 내용을 정리하고자 한다.
🤔 배경
문제를 탐구하기에 앞서서 하나씩 내가 무엇을 하려고 했는지 풀어나가고자 한다.
dropdown 디렉토리 구조
dropdown
├── Dropdown.tsx
├── DropdownItem.tsx
├── DropdownMenu.tsx
└── DropdownTrigger.tsx
내가 작성한 코드의 구조는 위와 같다. 그리고 코드는 아래와 같이 쓰여졌다.
Dropdown.tsx
import { createContext, ReactNode, useMemo, useState } from 'react';
import { DropdownTrigger } from '@/component/common/dropdown/DropdownTrigger.tsx';
import { DropdownItem } from '@/component/common/dropdown/DropdownItem.tsx';
import { DropdownMenu } from '@/component/common/dropdown/DropdownMenu.tsx';
interface IDropdownProps {
children: ReactNode;
}
export interface IToggleContext {
isOpen: boolean;
toggle: () => void;
}
export const ToggleContext = createContext<IToggleContext>({
isOpen: false,
toggle: () => {},
});
export const Dropdown = (props: IDropdownProps) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen((prevIsOpen) => !prevIsOpen);
const toggleContextValue = useMemo(() => ({ isOpen, toggle }), [isOpen]);
return (
<aside className="relative flex w-fit flex-col">
<ToggleContext.Provider value={toggleContextValue}>{props.children}</ToggleContext.Provider>
</aside>
);
};
Dropdown.Trigger = DropdownTrigger;
Dropdown.Item = DropdownItem;
Dropdown.Menu = DropdownMenu;
DropdownTrigger.tsx
import { ReactNode, useContext } from 'react';
import classNames from 'classnames';
import { ToggleContext } from '@/component/common/dropdown/Dropdown.tsx';
interface IDropdownTriggerProps {
/** 버튼 내부에 들어갈 컨텐츠 */
children: ReactNode;
}
/**
* 드롭다운 트리거 컴포넌트
* @remarks 드롭다운 트리거 컴포넌트입니다.
* @component
* @param {IDropdownTriggerProps} props.children 버튼 내부에 들어갈 컨텐츠
* @return {React.FunctionComponent}
* @example
* <DropdownTrigger>
* <MdMenu className="h-6 w-6" />
* <span>메뉴</span>
* </DropdownTrigger>
*
*/
export const DropdownTrigger = (props: IDropdownTriggerProps) => {
const { toggle } = useContext(ToggleContext);
const handleOnClick = () => {
toggle();
};
return (
<button
type="button"
className={classNames(
'flex',
'justify-center',
'items-center',
'bg-transparent',
'w-fit',
'h-fit'
)}
data-component="DropdownTrigger"
onClick={handleOnClick}
>
{props.children}
</button>
);
};