Material-UI の Snackbar の表示を外部から制御する方法のメモ。
(snackbar ... イベント完了時などにポップアップ表示されるやつ。)
やりたいこと
Material-UI の Snackbar には autoHideDuration というプロパティが用意されており、 autoHideDuration={2000} などと書けば2秒後に自動で onClose を呼んでくれる。
しかし、2秒立たないうちにSnackbarを再度開くイベントが発生したとき、一度閉じてまた開く動作をされてしまったので(そのまま保持してほしかった) open: boolean の値を外部からコントロールすることにした。
onCloseイベントを無視するようにすると一応これを防げたが、そもそもUIコンポーネントにtimerのような状態を持たせることが気持ち悪いので、open: booleanの値に応じて状態が定まるような fully-controlled component として snackbarを使いたい(≒autoHideDurationプロパティを使いたくない)。
結論
Rx(JS)を使うと以下のように書ける。 sourceEvent$ を snackbar を開くイベントを発火する Observable とする。
const snackbarIsOpen$: Observable<boolean> = sourceEvent$.pipe(
switchMap(() => timer(2000).pipe(mapTo(false), startWith(true)))
)
意味としては、sourceEvent$が発火するたびに (true)-----2000ms----->(false)という形のObservableを生成し switchMap する。
図にすると以下のようになる。
sourceEvent$
---o------o-o----------o-----------o---
~~~ ***Map(() => timer(2000).pipe(mapTo(false), startWith(true))) ~~~
---t------t-t----------t-----------t---
\ \ \ \ \
\ \ \ \ \
\ \ \ \ \
\ \ \ \ \
f f f f f
<---->
2000ms
~~~ switchMap ~~~
---t----f-t-t----f-----t----f------t----f---
switchMapの場合、後に生成される true---->falseが古いものを上書きするので、falseの前に次のtrueが発火すると古いfalseは捨てられる。
これにより、連続してイベントが発火したときにはsnackbarを閉じずに、イベントが2秒止んだタイミングで初めて閉じる動作が実現できる。
ちなみに
const snackbarIsOpen$: Observable<boolean> = sourceEvent$.pipe(
switchMap(() => timer(2000).pipe(mapTo(false), startWith(true)))
)
は
const snackbarIsOpen$: Observable<boolean> = sourceEvent$.pipe(
switchMapTo(timer(2000).pipe(mapTo(false), startWith(true)))
)
でも同じ意味のはず。
あとがき
ざっと書いたのでRxに慣れている人にしか読めないかもしれませんが、参考になれば幸いです。