【Vue.js】登録処理後、検索結果を維持した一覧画面に遷移し、かつ「登録完了」のトーストを表示させ・・・


それを共通化する

というお話です。

いきさつ

現在仕事でVue.jsを書いており、上記のことを頼まれ実装したので備忘録として。

使用しているライブラリ

・Vuex
・Vue Router
・BootstrapVue

流れ

こんな感じです。
・子コンポーネント(Main.vue)はウォッチャで前画面の情報をstoreに保持させている。
・孫コンポーネント(Form.vue)で登録ボタン押下、登録処理後にmixinsで呼び出したメソッドで一覧画面へ。
・親コンポーネント(app.vue)がクエリにtoast_typeがあることを検知すると、トーストを表示させる。
toast_typeがクエリにない一覧画面に遷移する。

今書いてて思いますが、絶対もっと良い方法があると思う・・・。

ソース

子コンポーネント

Main.vue

  watch: {
    '$route': function (to, from) {
      // 前画面情報を取得
      this.$store.commit('from_page_param/setFromPage', {
        full_path: from.fullPath,
        name: from.name,
        path: from.path
      })
    }
  }

store

from_page_param.js


export default {
  namespaced: true,
  state: {
    from_page: {
      full_path: '',
      path: '',
      name: ''
    }
  },

  mutations: {
    setFromPage (state, data) {
      state.from_page.full_path = data.full_path;
      state.from_page.path = data.path;
      state.from_page.name = data.name;
    }
  },

  getters: {
    fromPage: state => {
      return state.from_page;
    }
  }
}

ここまでが前画面情報保持。

孫コンポーネント

Form.vue
上部でmyMixin.vueをインポートし、mixinsで定義しているものとしてください。


      onClickSend () {
        this.$loading.load(this.$auth.api.patch(`/admin/hoges/create.json`, {
          hoge: this.hoge
        })
        .then(response => {
          this.transitionProcessed('created', 'IndexPageName');
        })
        .catch(error => {
          if(error.response.status == 422) {
            this.errors = error.response.data.errors;
          }else{
            this.$errorHandlers.ajax(error);
          }
        }))
      },

見るべきはthis.transitionProcessed('created', 'IndexPageName');
このメソッドはForm.vueでimportしてきたmyMixin.vueに書かれたメソッドです。
第一引数にはクエリ、toast_typeのvalue用、第二引数には遷移する一覧画面のNameを渡します。
この中身が・・・

ミックスイン

myMixin.vue

    // 登録処理後などの一覧画面遷移(検索結果保持用)
    transitionProcessed (toast_type, name) {
      let from_page = this.$store.getters['from_page_param/fromPage'];
      if(from_page.full_path && from_page.name === name) {
        this.$router.push({ path: from_page.full_path, query: { toast_type: toast_type } })
      }else{
        this.$router.push({ name: name, query: { toast_type: toast_type } })
      }
    },

前画面情報があり、戻りたい画面とこれから戻る画面が一致していれば
検索条件を保持してtoast_typeをクエリに追加した一覧画面に遷移します。

親コンポーネント

methods: {
    // クエリからtoast_typeを削除してreplace
    replaceDeletedToast (path) {
      let query = Object.assign({}, this.$route.query)
      delete query['toast_type']
      this.$router.replace({path: path, query: query})
    }
  },

  watch: {
    '$route': function (to, from) {
      if (!!this.$route.query.toast_type){
        if(this.$route.query.toast_type === 'updated') {
          this.$bvToast.toast("更新しました", {
            variant: 'success',
            title: '完了'
          });
        } else if(this.$route.query.toast_type === 'created') {
          this.$bvToast.toast("登録しました", {
            variant: 'success',
            title: '完了'
          });
        } else if(this.$route.query.toast_type === 'deleted') {
          this.$bvToast.toast("削除されました", {
            variant: 'success',
            title: '完了'
          });
        }
        this.replaceDeletedToast(to.path);
      }
    }
  }

toast_typeの中身で表示させるトーストを判断。
トースト表示後のURLはlocalhost/admin/hoges/?page=2&toast_type=createdtoast_typeが邪魔なので
this.replaceDeletedToast(to.path);の中で消し、replace!

結果

次からは非同期処理後にthis.transitionProcessed('created', 'IndexPageName');
だけ書けばOK

ハマったところ

app.vueのこの部分。実は最初replaceではなくpushを使用していた

this.$router.replace({path: path, query: query})

この場合ひとつ前の履歴はlocalhost/admin/hoges/?page=2&toast_type=createdのようなtoast_typeを含むパスとなり、ブラウザバックをすると、再びapp.vueがトースト表示させ、クエリを消して$router.push ... を延々と繰り返してしまう。

だがreplaceを使うことによってその遷移を履歴に残さないようにすることができ、結果的にブラウザバックでForm.vueの画面に戻ることができた。
これについてはこちらの記事を参考にしました。
JSでブラウザの履歴を操作する

あとがき

より良い方法をご存知の方がいらっしゃればコメントをくださると嬉しいです。
エンジニア歴1年記念日のQiita投稿でした。