Background
모달을 구현할 때 선언적(declarative) vs 명령형(imperative) 접근 간의 논의는 항상 뜨겁습니다.
// 선언적 모달의 사용
const App = () => {
const [isOpen, setIsOpen] = useState(false)
return (
<>
<button onClick={() => setIsOpen(true)}>Open</button>
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)} />
</>
)
}
// 명령형 모달의 사용
const handleClick = async () => {
const isConfirmed = await openModal(({ onClose }) => <Modal onClose={onClose} />)
// ...
}
- 선언적 모달: 컴포넌트 트리 안에서
isOpen
,onClose
같은 props로 상태를 드러내고 관리하기 쉽습니다. 하지만 특정 시점의 상호작용(예: “이 버튼 클릭 후 모달의 사용자 응답을 기다렸다가 다음 로직 진행”)을 구현하려면 코드가 장황해지거나 상태/콜백이 흩어집니다. - 명령형 모달:
open()
을 호출하고await
으로 결과를 받아 다음 로직을 자연스럽게 이어갈 수 있습니다. 다만 모달이 트리의 다른 곳에 렌더되기 때문에 일반적인 리액트 상태 흐름을 따르지 않습니다. 이는 Context API 사용이나 prop 전달을 어렵게 합니다.
use-async-flow
는 이 둘의 절충안을 지향합니다.
- 선언적으로 마운트: 모달은 컴포넌트 트리에서 렌더/제어됩니다. 스타일, 레이아웃은 App의 관심사로 유지됩니다.
- 명령적으로 컨트롤: 훅이
open() / resolve() / dismiss()
를 제공하고,open()
은 Promise로 결과를 돌려줍니다. 따라서 “사용자 응답을 기다린 뒤 다음 로직 실행” 같은 흐름을 간결하게 표현할 수 있습니다.
더불어 아래의 개선사항을 포함합니다.
- 예측 가능한 결과 모델: 결과는
resolved
(확정) 또는dismissed
(취소/닫힘)로 표준화되어 안전한 분기와 로깅이 가능합니다.
즉, 이 훅은 overlay(특히 모달)의 상태 관리 라이브러리입니다. 선언적 UI 구조를 유지하면서도, 명령형으로 “열기 → 기다림 → 닫힘/확정”의 사용자 흐름을 간결하게 작성하도록 돕습니다.
import { useAsyncFlow } from 'use-async-flow'
function Example() {
const overlay = useAsyncFlow<boolean, 'esc' | 'backdrop'>()
return (
<>
<button
onClick={async (e) => {
const result = await overlay.open(e)
if (result.status === 'resolved' && result.value === true) {
alert('confirmed!')
} else if (result.status === 'resolved' && result.value === false) {
alert('rejected!')
} else if (result.status === 'dismissed' && result.reason === 'esc') {
alert('esc pressed!')
} else if (result.status === 'dismissed' && result.reason === 'backdrop') {
alert('backdrop clicked!')
} else if (result.status === 'dismissed' && result.reason === 'unmount') {
alert('unmounted!')
}
}}
>
Open
</button>
<Modal
isOpen={overlay.isOpen}
onConfirm={() => overlay.resolve(true)}
onReject={() => overlay.resolve(false)}
onESCPress={() => overlay.dismiss('esc')}
onBackdropClick={() => overlay.dismiss('backdrop')}
/>
</>
)
}
Live Demo
다음은 use-async-flow
를 사용해 모달 2개를 순차적으로 열고 결과를 받는 데모입니다.
코드는 여기 에서 확인할 수 있습니다.
const r1 = await overlay1.open(event)
const r2 = await overlay2.open(event)
아래 버튼을 눌러 실제로 실행해보세요
상태모달1 closed모달2 closed
최근 결과
아직 결과가 없습니다.
Installation
npm install use-async-flow
API
자세한 내용은 API 레퍼런스 페이지를 확인하세요: API Reference
Last updated on