【Turbo/Stimulus】disconnectでBootstrapモーダルのdisposeメソッドを呼ぶのはなぜ？

## はじめに
これはHotwire.love meetup Vol.36であった議論をベースにして書いた記事です。

https://hotwire-love.connpass.com/event/330947/

以下のStimulusコントローラではdisconnectメソッドで、Bootstrap Modalのdisposeメソッドを呼び出しています。

```javascript 
import { Controller } from '@hotwired/stimulus';
import { Modal } from 'bootstrap';

export default class extends Controller {
  static targets = ['modal'];

  connect() {
    this.myModal = new Modal(this.modalTarget);
  }

  disconnect() {
    this.myModal.dispose(); // ここ！
  }

  show() {
    this.myModal.show();
  }

  hide(e) {
    if (e.detail.success && e.detail.formSubmission.submitter) {
      this.myModal.hide();
    }
  }
}
```

https://github.com/hotwire-love/touhyosan/blob/fb5e17327c981a0ce01df2019e25b3a3adc2e1f3/app/javascript/controllers/modal_controller.js#L12

https://getbootstrap.jp/docs/5.0/components/modal/#dispose

## 僕の疑問🤔
上記のコードについて、以下の点が気になったので、参加者のみなさんに聞いてみました。

- なぜdisposeメソッドを呼ばないといけないのか？
- Bootstrap Modal以外でもこういう後片付けの処理が必要なのか？

この質問に対して、勉強会の中で挙がった意見や回答を以下に載せます。

## なぜdisposeメソッドを呼ばないといけないのか？

- disposeメソッドを呼ばないとブラウザバック（またはブラウザフォワード）したときに問題が起きる可能性がある
- Turboはページ遷移するときにHTMLをキャッシュし、ブラウザバックで戻った際にそのキャッシュを再表示する
- このとき、中途半端にHTMLだけ復元されて、JavaScriptのイベントリスナーは復元されないことがある
- たとえばモーダルの場合、ブラウザバックで戻ってくるとモーダルは表示されているが、どこをクリックしても反応がない（＝イベントリスナーが何も設定されていない）ため、画面をリロードするしかない、といった問題が発生する可能性がある
- ページのDOMを変更するフロントエンドライブラリにはdisposeメソッドやdestroyメソッドといった名前の後片付け用のメソッドが用意されていることがある。こうしたメソッドを呼ぶと、TurboがHTMLをキャッシュする前にHTMLをDOMをいじる前の状態に戻してくれるので、ブラウザバックで戻ってきたときに「一切操作を受け付けないモーダルが表示されている」というような問題を避けられる（と、思ったが、そうならなかったので、その点は後述）

## Bootstrap Modal以外でもこういう後片付けの処理が必要なのか？

- HTMLがキャッシュされてブラウザバック時にそれがそのまま復元されて問題が起きるようなら後片付け（DOMを元に戻す処理）は必要。そうでなければ必ずしも必要でない。なのでケースバイケース
- DOMを操作してUIを変更するようなライブラリは後片付けした方が良いことが多い

## 注意点

- disposeやdestroyのようなメソッドを呼んだからといって、必ずしも問題が解決するわけではない
- 後片付けメソッドの実装はライブラリに任されているので、期待どおりの動きをしてくれる保証はない
- ライブラリによってはそもそも後片付け用のメソッドが用意されていないケースもある
- 後片付け用のメソッドを呼んでもブラウザバック時に問題が発生する場合は詳細な調査が必要だが、そういう調査は沼になりがち（経験者談）
- ブラウザバック時の不具合をどうしても解決できない場合はそのページをキャッシュしない、という手段で逃げる方法も検討する

## 実際にやってみた

以下のような操作をしてモーダルの挙動がどのように変わるか試してみました。

- モーダルを開く
- モーダルを開いたまま、ブラウザバックで前の画面に戻る
- ブラウザフォワードで元の画面に戻る

### disposeメソッドを呼ばない場合

たしかにブラウザフォワードでモーダルがそのまま表示される。「登録する」ボタンは動作するが、そのあと画面のどこをクリックしても反応がない。

![Untitled.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/18fde8b6-a53a-3bc7-8887-4ac50a3a7759.gif)

### disposeメソッドを呼んだ場合

disposeメソッドを呼ぶとご覧のとおり、上記の問題が・・・あれ？？全然変わってない😱

![modal2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/1bf5ef85-51f5-3d1f-2cde-4247c11ab563.gif)

今回のケースはどうやら上の注意点で書いた「diposeやdestroyのようなメソッドを呼んだからといって、必ずしも問題が解決するわけではない」に該当するようです💧

### Turboのキャッシュをオフにした場合

HTMLに以下のmetaタグを追加してTurboのキャッシュをオフにします。

```html 
<meta name="turbo-cache-control" content="no-cache">
```

https://turbo.hotwired.dev/handbook/building#opting-out-of-caching

この状態で操作すると、ブラウザフォワードで戻ってきてもHTML全体を再読み込みするため、モーダルが閉じた状態で画面が描画されました。また、モーダルの外をクリックすると、モーダルを閉じることもできました。
これはdiconnectでdisposeを呼んでも呼ばなくても同じ挙動でした。

![no-cache.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/998bdb7a-1aed-5326-1c1a-c2daf2391ffe.gif)

とりあえず、キャッシュをオフにすれば問題が解決することがわかりましたが、これだと逃げちゃった感が強いので、また別途時間をとってキャッシュをオフにしなくても問題を解決する方法を調査したいです！！

## まとめ

この記事のまとめです。

- Turboはページ遷移時にHTMLをキャッシュするため、それがブラウザバックやブラウザフォワードで予期しない挙動を引き起こす場合がある
- disconnect時にdisposeやdestroyのようなフロントエンドライブラリの後片付け用メソッドを呼び出すと、この問題を回避できる可能性がある（Turboがキャッシュする前にDOMを元に戻してくれるかもしれないので）
- が、後片付けメソッドを呼んだからといって必ずしも問題を回避できるとは限らない
- どうしても問題が解決しない場合はTurboのキャッシュをオフにするのも一手

本記事がTurboを利用している方の参考になれば幸いです！

### あわせて読みたい

https://techracho.bpsinc.jp/hachi8833/2025_01_21/147182

### PR: Hotwire.love は毎月第3 or 第４水曜開催！
Hotwire.love は Hotwire (Turbo / Stimulus / Hotwire Native) に関する話題をカジュアルに語り合うコミュニティです。

- Hotwire の学び方について
- Hotwire のハマりどころとその解決方法
- Turbo や Stimulus を活用したイケてる実装方法
- Hotwire の issue やリリースノートのチェック

などなど、雑多なトピックを参加者同士で話し合います。


次回は2024年10月23日（水）に開催予定です（イベントページは開催日の1〜2週間前に公開します）。

https://hotwire-love.connpass.com/

Howireを勉強し始めたばかりの初心者の方も大歓迎です！
「聞き専」枠もあるので、最初はちょっと様子を見てみたいという方もお気軽に参加ください :heart: