はじめに
数回に分けてNuxt.jsプロジェクトでデータの永続化を行う方法を解説しています。これらの記事は上から順番に読んでいくことを想定しています。
- Nuxt.jsプロジェクトでFirestoreを利用したデータの永続化/取り出しの実装方法
- Nuxt.jsプロジェクトでいろいろな条件を指定してFirestoreからデータを取得する
- Nuxt.jsプロジェクトでFirestoreのデータを変更/削除する
- Nuxt.jsプロジェクトでFirebase Storageに画像ファイルをアップロードする
- Nuxt.jsプロジェクトでFirebase Storageにアップロードした画像を変更/削除する
これらの記事で作成したNuxt.jsプロジェクトは以下のGitHubリポジトリーで公開しています。
GitHub - whitia/nuxt-firebase-sample
前回Firestoreを利用してデータを永続化しました。今回はそのデータを変更/削除する方法を解説します。
動的ページの作成
データの変更はドキュメント単位で行うので、変更画面をドキュメントごとに動的に作成する必要があります。ドキュメントを一意にできるid
フィールドを使うことにします。
pages/users/
の中にedit/
というディレクトリと、その中に_id.vue
というファイルを作成してください。pages/
ディレクトリの中は以下のようになります。
pages
-|users
--|edit # 追加
---|_id.vue # 追加
--|create.vue
--|index.vue
-|index.vue
アンダーバーから始まるファイルが動的ページになります。Nuxt.jsはこれだけで動的ルーティングが設定されます。.nuxt/router.js
を見てみてください。
routes: [{
path: "/users",
component: _6bde79c2,
name: "users"
}, {
path: "/users/create",
component: _c0882902,
name: "users-create"
}, {
path: "/users/edit/:id?",
component: _f7f74110,
name: "users-edit-id"
}, {
path: "/",
component: _d509f134,
name: "index"
}],
各ルーティングには名前がついています。今回は動的ページのusers-edit-id
を使いますので覚えておいてください。
pages/users/index.vue
を開き、以下のように追記します。
<template>
<div class="container mt-5">
<div class="row justify-content-center mb-3">
<div class="col-12 col-sm-3">
<h2>Users</h2>
</div>
</div>
<div class="row justify-content-center mb-3">
<div class="col-12 col-sm-2 font-weight-bold">Name</div>
<div class="col-12 col-sm-1 font-weight-bold">Age</div>
<!-- 以下を追記 -->
<div class="col-12 col-sm-1 font-weight-bold">Edit</div>
</div>
<div v-for="(user, key) in $store.getters.getUsers" :key="key"
class="row justify-content-center mb-3">
<div class="col-12 col-sm-2">
{{ user.name.first }} {{ user.name.last }}
</div>
<div class="col-12 col-sm-1">
{{ user.age }}
</div>
<!-- 以下を追記 -->
<div class="col-12 col-sm-1">
<nuxt-link :to="{ name: 'users-edit-id', params: { id: user.id } }">
Edit
</nuxt-link>
</div>
</div>
</div>
</template>
<!-- スクリプトは変更なし -->
ユーザー一覧画面に変更画面へのリンクを追加しました。ルーティングの名称にusers-edit-id
を指定し、パラメーターにuser.id
を渡しています。変更画面のURLは以下のようになっていると思います。
http://localhost:3000/users/edit/39e85916-899b-4d92-9200-8ceaa307249a
URLに一意なid
を入れることで動的ページを実現しています。このid
は変更画面に遷移してからも重要な意味を持ちます。
それでは変更画面を作っていきます。pages/users/edit/_id.vue
に以下を追記します。
※セキュリティ上、スクリプトタグを書くとコードが消えてしまうので、スクリプトタグをわざと書いていません。コピペする際は補完をお願いします。
export default {
created() {
const id = this.$route.params.id
this.$store.dispatch('fetchUser', { id })
}
}
this.$route
にはVue Routerによって渡されたいろいろな値が格納されています。ユーザー一覧画面のリンクで渡したuser.id
はthis.$route.params.id
に入っています。この値を使ってユーザー情報を取得しに行きます。
store/index.js
に以下を追記します。
...
export const state = () => ({
...
user: {
id: '',
name: {
first: '',
last: ''
},
age: ''
}
})
export const actions = {
...
fetchUser({ commit }, payload) {
return new Promise((resolve, reject) => {
usersRef.where('id', '==', payload.id).get()
.then(res => {
res.forEach((doc) => {
commit('addUser', doc.data())
resolve(true)
})
})
.catch(error => {
console.error('An error occurred in fetchUsers(): ', error)
reject(error)
})
})
}
}
export const mutations = {
...
addUser(state, user) {
state.user = user
}
}
export const getters = {
...
getUser(state) {
return state.user
}
}
fetchUser
メソッドは受け取ったid
を条件にしてFirestoreからユーザー情報を取得します。取得したデータはVuexストアのデータ領域に保存します。また、getUser
メソッドを作成して外部から参照可能にしておきます。
pages/users/edit/_id.vue
に戻り、テンプレートを追記します。
<template>
<b-form @submit.prevent="editUser">
<div class="container mt-5">
<div class="row justify-content-center mb-3">
<div class="col-12 col-sm-3">
<h2>Edit user</h2>
</div>
</div>
<div class="row justify-content-center">
<div class="col-12 col-sm-3">
<b-form-group label="First name" label-for="first">
<b-form-input
id="first"
:value="$store.getters.getUser.name.first"
required
placeholder="Jane"
></b-form-input>
</b-form-group>
</div>
</div>
<div class="row justify-content-center">
<div class="col-12 col-sm-3">
<b-form-group label="Last name" label-for="last">
<b-form-input
id="last"
:value="$store.getters.getUser.name.last"
required
placeholder="Doe"
></b-form-input>
</b-form-group>
</div>
</div>
<div class="row justify-content-center">
<div class="col-12 col-sm-3">
<b-form-group label="Age" label-for="age">
<b-form-input
id="age"
:value="$store.getters.getUser.age"
required
placeholder="20"
></b-form-input>
</b-form-group>
</div>
</div>
<div class="row justify-content-center mt-3">
<div class="col-12 col-sm-3">
<b-button block type="submit" variant="primary">Edit</b-button>
</div>
</div>
</div>
</b-form>
</template>
新規作成画面ではv-model
を使っていましたが、変更画面では:value
を使っていることに注意してください。v-model
を使ってVuexストアのデータをバインドしてしまうと、値を変更したときに「Vuexストアのデータはmutations
を使って変更せよ」というエラーが出てしまいます。
長くなりましたが、ようやく動的ページができました。次からデータの変更処理を実装していきます。
データの変更
pages/users/edit/_id.vue
に以下を追記します。
<!-- テンプレートは変更なし -->
export default {
...
methods: {
editUser(e) {
const user = {
id: this.$store.getters.getUser.id,
name: {
first: e.target.first.value,
last: e.target.last.value
},
age: e.target.age.value
}
this.$store.dispatch('editUser', { user })
.then(() => {
setTimeout(() => {
this.$router.push('/users')
}, 1000)
})
}
}
}
入力フォームに入力された値を使ってVuexのアクションを呼び出します。
editUser
メソッドが完了してから1秒後にユーザー一覧画面に遷移しています。これはなぜかというと、データ変更後にすぐ遷移してしまうとFirestoreにデータ変更が反映されないためです。
store/index.js
に以下を追記します。
...
export const actions = {
...
editUser({ commit }, payload) {
return new Promise((resolve, reject) => {
usersRef.where('id', '==', payload.user.id).get()
.then(snapshot => {
snapshot.forEach(doc => {
const user = {
id: uuidv4(),
name: payload.user.name,
age: payload.user.age,
updated_at: firebase.firestore.FieldValue.serverTimestamp()
}
usersRef.doc(doc.id).update(user)
.then(ref => {
resolve(true)
})
.catch(error => {
console.error('An error occurred in editUser(): ', error)
resolve(error)
})
})
})
})
}
}
...
これでデータ変更処理ができました。
データの削除
最後にデータの削除処理を実装します。pages/users/index.vue
に以下を追記します。
<template>
<div class="container mt-5">
<div class="row justify-content-center mb-3">
<div class="col-12 col-sm-3">
<h2>Users</h2>
</div>
</div>
<div class="row justify-content-center mb-3">
<div class="col-12 col-sm-2 font-weight-bold">Name</div>
<div class="col-12 col-sm-1 font-weight-bold">Age</div>
<div class="col-12 col-sm-1 font-weight-bold">Edit</div>
<!-- 以下を追記 -->
<div class="col-12 col-sm-1 font-weight-bold">Delete</div>
</div>
<div v-for="(user, key) in $store.getters.getUsers" :key="key"
class="row justify-content-center mb-3">
<div class="col-12 col-sm-2">
{{ user.name.first }} {{ user.name.last }}
</div>
<div class="col-12 col-sm-1">
{{ user.age }}
</div>
<div class="col-12 col-sm-1">
<nuxt-link :to="{ name: 'users-edit-id', params: { id: user.id } }">
Edit
</nuxt-link>
</div>
<!-- 以下を追記 -->
<div class="col-12 col-sm-1">
<a href="#" @click.prevent="deleteUser(user.id)">Delete</a>
</div>
</div>
</div>
</template>
export default {
...
methods: {
deleteUser(id) {
if (!confirm('Are you sure?')) return
this.$store.dispatch('deleteUser', { id })
.then(() => {
setTimeout(() => {
this.$store.dispatch('fetchUsers')
}, 1000)
})
}
}
}
deleteUser
メソッドの引数にuser.id
を渡しています。そのid
をそのままVuexストアのアクションに投げます。
store/index.js
に以下を追記します。
export const actions = {
...
deleteUser({ commit }, payload) {
return new Promise((resolve, reject) => {
usersRef.where('id', '==', payload.id).get()
.then(snapshot => {
snapshot.forEach(doc => {
usersRef.doc(doc.id).delete()
.then(ref => {
resolve(true)
})
.catch(error => {
console.error('An error occurred in deleteUser(): ', error)
reject(error)
})
})
})
})
}
}
...
これで削除処理ができました。
まとめ
Firestoreのデータを変更/削除する方法を解説しました。
ここまで実装できればFirestoreの操作はマスターしたと言ってもいいんじゃないでしょうか。Firestoreとの間には必ずVuexストアが入るという流れを覚えておいてください。
また、Vuexストアのデータ領域はコンポーネントをまたいで参照できます。ページをリロードしてDOMを再生成しない限り残り続けます。ログイン情報など、ページをリロードしても残しておきたいデータはブラウザのローカルストレージに保存する方法があります。Nuxt.jsでローカルストレージを利用する方法についてはまた別記事にてまとめたいと思います。