Closed18

Next.jsとCloudFirestoreの連携

ikmzkroikmzkro

設定ページのカテゴリの追加機能にて、
Cloud Firestoreのcategoriesコレクションと連携されている仕組みを理解したい。

ikmzkroikmzkro

コレクション:categories/ドキュメントID/フィールド:name,time,userId

nameはタスク名(カテゴリ)、
timeはタイマーの持続時間、
userIdは一時的に(12345678)としている。(認証機能を適応させた後に変更する想定)

ikmzkroikmzkro

Firebaseの利用に必要な手順
1.Firebaseを利用するための初期化
2.サービスを利用するための具体的な処理

ikmzkroikmzkro

pc/lib/db.js

FirebaseのAPIを取り出す

import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/storage';

最終的にFirebaseから抽出するデータ格納先と容量を用意する

let db;
let storage;
ikmzkroikmzkro

設定情報を用意し、セキュリティ対策のため詳細をenvlocalファイルで設定する

const config = {
    apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
    authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
    projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
    storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
    measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
  };

設定できたのでFirebaseのinitializeAppメソッドを呼び出す(引数に設定情報configを渡す)。

FirebaseのinitializeAppメソッドで生成されたアプリはfirebase.appsに格納されていくため、
firebase.appsにデータがゼロの場合のみ初期化する

  if (!firebase.apps.length) {
    firebase.initializeApp(config);
  }
ikmzkroikmzkro

firebaseからデータとストレージを持ってくる

db = firebase.firestore();
storage = firebase.app().storage('gs://mitan-timer-dev.appspot.com');

Node.jsのお作法module.exports = {値};
任意ファイルに存在する変数や関数を、別ファイルで実行可能になる。

以下のように書くと別ファイルでimport文を書いてdb, storageの値を適切に扱えば、
アプリケーションとFirebaseとのDB連携が可能になる。

module.exports = { db, storage };
ikmzkroikmzkro

pc/src/util/firebase.js

pc/lib/db.jsからFirebase(Cloud Firestore)にアクセスするための情報を持つ値dbを持ってくる

import { db } from '../../lib/db';

この配下にクラスを独自で定義して、内部のオブジェクト(主にメソッド)を書いていく。

ikmzkroikmzkro

Firebaseという独自クラスを用意して、
最初のconstructorメソッドではCloud Firestoreのコレクション(データの大元)を初期化している

class Firebase {
  constructor() {
    this.users = db.collection('users');
    this.categories = db.collection('categories');
    this.sounds = db.collection('sounds');
  }
ikmzkroikmzkro

setting.tsxにて、
扱うpc/lib/db.jsの関数

getUserId()
getCategories()
updateCategory()
createCategory()
deleteCategory()
ikmzkroikmzkro

getUserIdメソッドは非同期処理で、
この関数を呼び出すと、文字列'12345678'が返される

  getUserId = async () => {
    return '12345678'; //ログイン認証するまでの仮
  };
ikmzkroikmzkro

setting.tsxでの処理内容は以下の通り。
1.変数userIdgetUserIdメソッドで呼び出した値を代入
2.setUserIdメソッドでステートに格納して値を更新
また、非同期でgetCategoriesメソッドを呼び出して引数にuserIdを渡している。

  useEffect(() => {
    async function fetchData() {
      const userId = await firebase.getUserId(); //テストId
      setUserId(userId);
      await getCategories(userId);
    }
    fetchData();
  }, []);
ikmzkroikmzkro

getCategoriesメソッド
アロー関数の引数にはuserIdを渡して、処理内容は以下の通り。

1.データを格納するdataという配列を用意
2.categories内のuserIdの値と、getUserIdメソッドで取得したuserIdの値が同値である場合に、
refにuserIdの値を格納
3.querySnapshotメソッドを呼び出して配列dataに対して{ ...doc.data(), id: doc.id }を格納
4.最後に新しく生成されたdataを返却

  getCategories = async (userId) => {
    let data = [];
    const ref = this.categories.where('userId', '==', userId);
    try {
      await ref.get().then(function (querySnapshot) {
        querySnapshot.forEach(function (doc) {
          data.push({ ...doc.data(), id: doc.id });
        });
      });
      return data;
    } catch (e) {
      throw e;
    }
  };

参考記事

ikmzkroikmzkro

setting.tsxでは、
アロー関数の引数にuserIdを渡して、処理内容は以下の通り。
1.変数categoriesにgetCategoriesで取得したdataを代入
2.setCategoriesメソッドでcategoriesの値を更新
3.setSelectedOptionメソッドでselectedOptionの値(name, time, id .userId)を更新
※返却されたdataはquerySnapshotで生成された配列型なのでcategories[0]
4.setTimeメソッドでtimeの値を更新
※categories[0].timeで取得したカテゴリーのtimeを抽出できる

  const getCategories = async (userId: any) => {
    try {
      const categories = await firebase.getCategories(userId);
      setCategories(categories);
      setSelectedOption(categories[0]);
      setTime(categories[0].time);
    } catch (e) {
      console.error(e);
    }
  };
ikmzkroikmzkro

updateCategory()メソッド
第1引数のidに一致するドキュメントに対して、第2引数のdataを上書きする

  updateCategory = async (id, data) => {
    try {
      await this.categories.doc(id).update(data);
    } catch (e) {
      throw e;
    }
  };
ikmzkroikmzkro

createCategory()メソッド
Cloud Firestore/categoriesにランダムIdを生成し、その配下に引数のdataをセット

  createCategory = async (data) => {
    try {
      await this.categories.doc().set(data);
    } catch (e) {
      throw e;
    }
  };
ikmzkroikmzkro

setting.tsxでは、
handleModalChangeメソッドのアロー関数の引数にname, timeを渡して、処理内容は以下の通り。

0.条件分岐の設定値が型どおりであるなら??意味不明
1.変数categoryDataに対して、更新されたプロパティと値を設定
2.updateCategory()メソッドにselectedOption.idcategoryDataを渡して、データを上書き
3.getCategories()メソッドで新たなdataを返却
4.それ以外なら??意味不明
5.変数categoryDataに対して、更新されたプロパティと値を設定
6.createCategory()メソッドで新規ドキュメントIdを加えcategoryData`を渡しデータを上書き
7.getCategories()メソッドで新たなdataを返却

  const handleModalChange = async (name, time) => {
    if (userId) {
      if (selectedOption) {
        //カテゴリーの編集
        const categoryData = { name: name, time: time, userId: userId };
        await firebase.updateCategory(selectedOption.id, categoryData);
        await getCategories(userId);
      } else {
        //カテゴリーの追加
        const categoryData = { name: name, time: time, userId: userId };
        await firebase.createCategory(categoryData);
        await getCategories(userId);
      }
    }
    setOpen(false);
  };
ikmzkroikmzkro

deleteCategory()メソッド
引数のidに対応するCloud Firestore/categories/ドキュメント(配下のフィールドの値)を削除

  deleteCategory = async (id) => {
    try {
      await this.categories.doc(id).delete();
    } catch (e) {
      throw e;
    }
  };
ikmzkroikmzkro

setting.tsxでは、処理内容は以下の通り。

1.deleteCategory()メソッドを呼んで指定idのカテゴリを削除
2.getCategories()メソッドで新たなdataを返却

  const handleListItemDelete = async (id: string) => {
    try {
      await firebase.deleteCategory(id);
      await getCategories(userId);
    } catch (e) {
      console.error(e);
    }
  };
このスクラップは2021/09/12にクローズされました