【メモ】Nuxt.js: axiosでオフライン状態(NetworkError)を捕捉する
axiosでAPIにアクセスするとき、エラーレスポンスをハンドリングすることは出来るんだけど、接続エラーなどの、サーバーからレスポンスが何も帰ってこないエラー(ネットワークエラー)は onError
にも onResponse
にも入ってこない。
// @/plugins/axios.js export default function({ $axios }) { $axios.onRequest(req => { // リクエスト前に呼び出されるコード }) $axios.onResponse(res => { // 成功レスポンスを受け取った時に呼び出されるコード // 接続エラーはここにこない(レスポンス自体ないから) }) $axios.onError(res => { // エラーレスポンスを受け取った時に呼び出されるコード // 接続エラーはここにこない(レスポンス自体ないから) }) }
一日考えた挙句、そうかリクエスト前にチェックすれば良いんだわということに気が付いた(遅)
window.navigator.onLine
で、オンライン状態を確認できるので、onRequest
にこのチェックを追加。
// @/plugins/axios.js import Vue from 'vue' export default function({ $axios }) { $axios.onRequest(req => { if (!window.navigator.onLine) { Vue.toasted.error( // オフライン時にAPIリクエストがあったときにtoastを表示するようにした '現在オフラインです。' ) return Promise.reject(req) } // リクエスト前に呼び出されるコード }) $axios.onResponse(res => { // 成功レスポンスを受け取った時に呼び出されるコード }) $axios.onError(res => { // エラーレスポンスを受け取った時に呼び出されるコード }) }
無事捕捉することが出来ました。
Vue Test Utilsでコンポーネントのdata()を評価したい
なぜかうまくいかず時間がかかったのでメモ
こういうページネーションのコンポーネントのテストを書くとき、
コンポーネントのpropsには以下のような値(現在のページ,ページあたりの件数,トータル件数)を設定できるとして
<Pagination :config="{ current: 1, limit: 20, total: 100 }" @movePage="load" />
propsに値を設定したことを受け、watch
に定義したメソッドで、data()
の最終ページ lastPage
の値を算出したい。
watch: { config: function(payload) { // ここではわかりやすく「5ページ >>」とか変数にいれてる this.lastPage = Math.ceil(payload.total / payload.limit) + 'ページ >>' // 上記例の設定値だと「5ページ >>」になる } }
この「5ページ >>」を検査したかったのだけど、テストコードで propsData
をセットしてもなぜかwatchが発火しない。
<template> ... <a class="pagination-link last-page" :disabled="currentPage === lastPage" aria-label="Goto last page" @click="moveLast" > {{ lastPage }} <!-- 「5ページ >>」 --> </a> ... </template>
describe('components/Pagination.vue', () => { let wrapper describe('template', () => { beforeEach(() => { wrapper = mount(Pagination, { propsData: { config: { nextPage: 2, limit: 20, total: 100 } } // propsにセットしたのだからwatch発動してほしい! }) }) test('最終ページは5ページ目であること', () => { const page = wrapper.find('.last-page') expect(page.text()).toBe('5ページ >>') // 失敗。got:null }) }) })
props
を2回セットしたら思った通り動くようになった。
describe('components/Pagination.vue', () => { let wrapper describe('template', () => { beforeEach(() => { wrapper = mount(Pagination, { propsData: { config: { nextPage: 2, limit: 20, total: 100 } } }) }) test('最終ページは5ページ目であること', () => { wrapper.setProps({ config: { nextPage: 2, limit: 20, total: 100 } }) // これを足した。 const page = wrapper.find('.last-page') expect(page.text()).toBe('5ページ >>') // 成功した! }) }) })
2度setProps
していてなんかモヤるけど、やりたいことはできた。
【メモ】Nuxt.js/BulmaのNavbarでdropdownが残ってしまう(:focus-withinが効きっぱなし)
よくあるSPAのドロップダウン。
<n-link class="navbar-link" to="/members"> <span class="icon is-medium"> <i class="fas fa-lg fa-user"></i> </span> <span>メンバー管理</span> </n-link> <div class="navbar-dropdown"> <n-link class="navbar-item" to="/members">メンバー一覧</n-link> <n-link class="navbar-item" to="/members/new">メンバー登録</n-link> </div>
これ、なぜか n-link
click後にフォーカス状態が残ってしまい、閉じてくれない。
これは @click.native
イベントに一行追加することで、閉じてくれるようになりました(手元のChromeとFirefoxで確認)。
<n-link class="navbar-link" to="/members" @click.native="hideMenu"> <span class="icon is-medium"> <i class="fas fa-lg fa-user"></i> </span> <span>メンバー管理</span> </n-link> <div class="navbar-dropdown"> <n-link class="navbar-item" to="/members" @click.native="hideMenu">メンバー一覧</n-link> <n-link class="navbar-item" to="/members/new" @click.native="hideMenu">メンバー登録</n-link> </div>
んで、このメソッドを追加。
<script> export default { methods: { hideMenu(e) { e.target.blur() } } } </script>
以上です。
この一行を追加するまで、謎にえらい時間を費やしてしまったので、メモとして残します。
【メモ】Vueコンポーネントのwatchがwatchしない
こういうやつ
// NG watch: { config: (val, oldVal) => { console.log(val) console.log(oldVal) } }
functionにする必要があります。
// OK watch: { config: function(val, oldVal) { // functionにする console.log(val) console.log(oldVal) } }
ウォッチャを定義するためにアロー関数を使用すべきではない
と、ドキュメントにちゃんと、しかも太字で書いてありました。。サーセン
【メモ】Vueコンポーネントで再帰的に$emitしたいときの引数
Vueコンポーネントの中で自身のコンポーネントを呼び出してツリー表示をしたいときがあると思います。
今回やりたかったのは以下のこと。
1) 初期表示で最上位の階層(親のいない階層)を表示
2) 子要素があれば hasChild
属性が true
。「+」ボタンを表示する。
3)「+」ボタンを押したら、 loadChildren
メソッドを $emit
。自身の children
属性に子要素リストを読み込んで表示する。
4) 2-4を繰り返す。
というもの。再帰のところで $emit
の引数に何を渡すんだろう?とひるみましたが、 $event
を渡せばよいんだそうです。
<!-- Tree.vue --> <template> <div class="tree"> <ul v-for="item in items" :key="item.id"> <li> {{ item.name }} <Tree v-if="item.children !== null" // 子要素があるときだけ機能する :items="item.children" @loadChildren="$emit('loadChildren', $event)" // 引数に $event を指定する /> <a v-else-if="item.hasChild" @click="$emit('loadChildren', item)"> // 引数に item を指定する + </a> </li> </ul> </div> </template>
ここに例がありました。
【メモ】Nuxt.js で「Uncaught Error: [nuxt] store/index.js should export a method that returns a Vuex instance.」
storeの中に new Vuex.Store
を返す index.js
を作成したらエラー
ファイル名を store/index.js
から store/store.js
に変えたら直った。
store/index.js
はNuxtの中では特別な意味があるファイルなのか。