きっかけ
「コピーしました」のような一瞬で消えるToastを、MUIのSnackbarで出す実装をしていた時なんですが、なんだか想定より長く画面に残っている気がして、実際に測ってみたらautoHideDurationに3000を渡しているのに倍の6秒間も表示されていました。
エラーが出ているわけでもないので、この手の細かい挙動はつい後回しにしがちなんですが、6秒も残るとなるとさすがに気になったので原因を調べてみました。
同じように詰まった人がすぐ抜けられるよう、記事に残しておきます。
元のコード
const showSnackbar = (message: string) => {
setSnackbarMessage(message)
setSnackbarOpen(true)
}
const resetSnackbar = () => {
setTimeout(() => {
setSnackbarOpen(false)
setSnackbarMessage('')
}, 3000)
}
JSX側ではこう使っていました。
<Snackbar
open={snackbarOpen}
autoHideDuration={3000}
onClose={resetSnackbar}
message={snackbarMessage}
/>
3000msで閉じると書いてあるのでパッと見はおかしくないはずです。
タイマーが二重に走っていた
引っかかっていたのはautoHideDuration={3000}のほうでした。
ドキュメントを見ると、これはMUIのSnackbarが自前で持っているタイマーで、「開いてから3秒経ったらonCloseを呼ぶ」という意味のようです。
閉じるのではなく、あくまでonCloseを呼ぶだけ、というところがポイントでした。自動で閉じるための仕組みは、MUI側にすでにあるわけです。
ところが、そのonCloseに渡したresetSnackbarのほうも、さらにsetTimeoutで3秒待ってからopenをfalseにしていました。
流れを整理するとこうなります。
- 開く
- MUIが3秒カウントして
onClose(=resetSnackbar)を呼ぶ -
resetSnackbarがそこからさらに3秒待ってsetSnackbarOpen(false)
3秒 + 3秒 で6秒かかっていた、ということです。
autoHideDurationは「何秒後にonCloseを呼ぶか」であって「何秒後に閉じるか」ではない、というところを取り違えていました。
修正後
onCloseは即座に閉じるだけにして、ディレイはMUIに任せるようにしました。
const resetSnackbar = () => {
setSnackbarOpen(false)
setSnackbarMessage('')
}
比較
修正前と修正後を並べてみました。左がタイマーを二重に持っていたころで、右がsetTimeoutを消したあとです。
こうして並べると、たった3秒の違いでも、左のほうはずいぶん長く感じます。
トーストが残りすぎると、次の操作の邪魔になったり「まだ処理中かな」と勘違いさせたりと、ユーザー体験を損ねてしまいます。
こういう細かいところの積み重ねが使い心地に影響するので、面倒でも一つずつ潰していきたいですね。
振り返り
原因はautoHideDurationを「閉じるまでの時間」だと思い込んでいたことで、直したのはsetTimeoutを一行消しただけでした。
ライブラリが自前でタイマーを持っているところに同じ役割の処理をもう一枚足すと二重になってしまうので、どこまで任せられるかは先に確かめておきたいところです。
