Modal Queue

アプリケーションUIには、画面を覆い被しユーザー操作を限定する global UI があります。Modal がその代表的なもので、管轄する Queue をアプリケーションで1つ構えておくと有用です。2重表示に悩む事なく、表示待ち行列を操作したり、要件拡張の際に柔軟に対応することが出来ます。
code: [github](https://github.com/takefumi-yoshii/react-hooks-ogiri/tree/master/apps/modal-queue) / `$ yarn 1223`

|![1223-1.jpg](https://qiita-image-store.s3.amazonaws.com/0/59071/82679e8f-3613-4076-bd32-49d4c0df3822.jpeg)|![1223.jpg](https://qiita-image-store.s3.amazonaws.com/0/59071/048ff04a-5e94-9f18-f7b5-3f3a47485833.jpeg)|![1223-2.jpg](https://qiita-image-store.s3.amazonaws.com/0/59071/b8f9d954-f654-7715-059c-371bc0252dee.jpeg)|
|---|---|---|

文字入力で動的に Modal を登録、再生で登録済みのモーダルを順次表示するサンプルを紹介します。

# useModalQueue

Queue を扱う Custom Hooks の内訳です。

```components/useModalQueue.ts
const useModalQueue = () => {
  const [state, update] = useState<State>(...)
  const renderModal = useMemo(...) // 表示する QueueItem の参照
  const handleRegisterModal = useCallback(...) // QueueItem の登録
  const handlePlayQueue = useCallback(...) // 再生モードのスタート
  return {
    state,
    renderModal,
    handleRegisterModal,
    handlePlayQueue
  }
}
```

Queue に ModalItem を追加するハンドラです。form 要素の submit にこの処理をバインドします。`registerModal` 関数では、配列に JSX.Element を追加しています。

```components/useModalQueue.ts
const handleRegisterModal = useCallback(
  (value: string) => {
    const handleClose = () => {
      update(_state => {
        const items = [..._state.items]
        items.shift()
        const isOpen = items.length !== 0
        return { ..._state, items, isOpen }
      })
    }
    update(_state => {
      const newItem = registerModal({ // JSX.Element の生成
        createdAt: new Date(),
        message: value,
        handleClose
      })
      const items = [..._state.items, newItem]
      return { ..._state, items }
    })
  },
  []
)
```

こちらが生成関数です。コンポーネントをマウントせずに要素を配列に追加します。

```components/registerModal.tsx
const registerModal = (props: Props) => (
  <Modal
    createdAt={props.createdAt}
    handleClose={props.handleClose}
    message={props.message}
    bgColor={'rgba(0,0,0,.5)'}
  />
)
```
この `registerModal` 関数では、同じ Component を返しています。要件に応じて様々な種別の Modal を return すると良いでしょう。

# Queue の再生

`handlePlayQueue` が発火されると、登録された QueueItem の先頭要素から再生が始まります。`renderModal` は、`useModalQueue` Custom Hooks から得られる、QueueItem の先頭要素参照です。

```components/index.tsx
const View = (props: Props) => {
  const {
    state,
    renderModal,
    handleRegisterModal,
    handlePlayQueue
  } = useModalQueue()
  const isEnablePlayQueue = useMemo(
    () => state.items.length !== 0,
    [state.items]
  )
  return (
    <div className={props.className}>
      <Input handleSubmit={handleRegisterModal} />
      <PlayModalQueue
        handlePlay={handlePlayQueue}
        queueCount={state.items.length}
        isEnablePlayQueue={isEnablePlayQueue}
      />
      {state.isOpen && renderModal} // here
    </div>
  )
}
```

あらかじめ `registerModal` で確保されたコンポーネント配列の先頭を返します。先頭以外にも要件に応じて、次に表示するべきモーダルを自由に選ぶことが出来ますね。

```components/useModalQueue.ts
const renderModal = useMemo(() => state.items[0], [
  state.items
])
```

# useModal

こちらは Modalコンポーネントで利用している Custom Hooks です。表示された時のアニメーションやスタイルを取り扱っているのみなので、Queue の処理とは関係ありません。

```components/modal/useModal.ts
const useModal = (props: Props) => {
  const [state, update] = useState(...)
  const options = useMemo(...)
  const bgStyle = useMemo(...)
  const modalStyle = useMemo(...)
  useEffect(...)
  return {
    bgStyle,
    modalStyle
  }
}
```