【React × Firebase】CRUDを学ぶ

本記事は、firebaseでのCRUDのクエリの書き方を手っ取り早く知りたい方のために書きました。

はじめに

Reactで使用するFirebaseのJavaScriptのSDKは2021年8月25日にv8→v9へメジャーバージョンアップデートが行われています。

そのため、Firebaseの記事を参考にする際は上記日以降にアップされているものを参照することをおすすめします。

本記事は、2023年1月9日時点の最新であるv9.15.0をもとに作成しています。

環境

  • React @18.2.0
  • Firebase @9.15.0

それでは行きましょう!

プロジェクトの作成

Reactプロジェクトの作成

% yarn create react-app react-firebase-crud

% cd react-firebase-crud

% yarn start

Firebaseプロジェクトの作成

詳細は割愛しますが、ここでは以下内容で作成しました。

  1. プロジェクト名「react-firebase-crud」
  2. webアプリを作成
    アプリのニックネームも「react-firebase-crud」として登録
  3. firebase SDKのinstall
% yarn add firebase

4. Cloud Firestoreを本番環境モードで作成する

Cloud Firestoreのルールについてはこちらの公式ドキュメントを参照ください。

デフォルトでは以下のルールとなっており、全てのリクエストが拒否される設定となっています。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

readとwriteはそれぞれ以下を意味しています。

  • read: その名の通りread
  • write: create, update, deleteの3つの要素を含む
    これは細かく分割することも可能。こちらのドキュメントを参照

その他には、よく使われるものとしては以下のような設定があります。

  • 認証ユーザであればCRUD全てが可能
allow read, write: if request.auth != null;
  • 自分がcreateしたデータのみを読み書き可能
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }

初期設定

ReactとFirebaseのプロジェクトの作成ができましたので、諸々の初期設定を行います。

  • srcディレクトリ配下にfirebase-config.js ファイルを作成し、firebaseでコンソールで表示されたfirebaseConfigを作成したファイルにコピペする
  • Cloud Firestoreのルールを、全てのリクエストを許可するため”true”に変更する
allow read, write: if true;
  • collectionを仮で以下内容で作成する
    ここではStephen, Brandon, Jeffの3人を登録しました。
  • Firestoreのデータを参照するために、変数をdbとしてfirebase-config.jsに設定を行います。
    公式ドキュメントはこちら
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";

const firebaseConfig = {
    // ...
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);

// Initialize Cloud Firestore and get a reference to the service
export const db = getFirestore(app);

以上でFirebaseとの連携の設定は完了したので、本題のCRUDを行っていきます。

CRUDを作成する

Create React Appで自動生成されたファイルの中のApp.jsに編集を加えていきます。

Read

まずはReadから。先程collectionを作成したのでそのデータを取得します。

リスト表示をするため、users collectionの全てのdocumentを取得します。

こちらの公式ドキュメントでは以下のように記載されていますがこちらを少しいじる必要があります。

import { collection, getDocs } from "firebase/firestore";

const querySnapshot = await getDocs(collection(db, "cities"));
querySnapshot.forEach((doc) => {
  // doc.data() is never undefined for query doc snapshots
  console.log(doc.id, " => ", doc.data());
});

App.jsを以下のように書き換えます。

import { collection, getDocs } from "firebase/firestore";
import { useEffect, useState } from "react";
import "./App.css";
import { db } from "./firebase-config";

function App() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const getUsers = async () => {
      // getDocsを使用して、指定されたcollection内、ここではusersの全てのデータを取得する
      const querySnapshot = await getDocs(collection(db, "users"));

      setUsers(
        querySnapshot.docs.map((doc) => ({
          ...doc.data(), // スプレッド構文を使い、nameとageを取得
          id: doc.id, // document idを付与する
        }))
      );
    };
    getUsers();
  }, []);

  return (
    <div className="App">
      {users.map((user) => {
        return (
          <div key={user.id}>
            <p>Name: {user.name}</p>
            <p>Age: {user.age}</p>
          </div>
        );
      })}
    </div>
  );
}

export default App;

公式ドキュメントのforEachを使った方法では、データの取得はできるんですが、配列形式で取得をしたいので、mapを使って取得します。

forEachをmapに置き換えると以下のように書くことができます。

setUsers(querySnapshot.docs.map((doc) => doc.data()));

しかし、こうすると以下の警告文が出ます。

Warning: Each child in a list should have a unique “key” prop.

そのため、一意を特定するためのidを振って上げる必要があるのでオブジェクトにidを追加し、以下の様に記述します。userのオブジェクトには、もともとnameとageが入っているので、それをスプレッド構文で表し、そこにidプロパティを追加しています。

      setUsers(
        querySnapshot.docs.map((doc) => ({
          ...doc.data(), // スプレッド構文を使い、nameとageを取得
          id: doc.id, // document idを付与する
        }))
      );

これで、上記のエラーは解消し、以下のような画面となります。

Create

Createをする方法は、主に以下2つの方法があります。

この2つの違いは、setDocはCreate時にdocumentのIDを指定するのに対し、addDocは自動生成IDを付与してくれます。

ここでは、addDocを使った方法で作成します。

import { addDoc, collection, getDocs } from "firebase/firestore";
import { useEffect, useState } from "react";
import "./App.css";
import { db } from "./firebase-config";

function App() {
  const [newName, setNewName] = useState("");
  const [newAge, setNewAge] = useState(0);
  const [users, setUsers] = useState([]);

  const usersCollectionRef = collection(db, "users");

  const createUser = async () => {
    await addDoc(usersCollectionRef, {
      name: newName,
      age: newAge,
    });
  };

  useEffect(() => {
    const getUsers = async () => {
      // getDocsを使用して、指定されたcollection内、ここではusersの全てのデータを取得する
      const querySnapshot = await getDocs(usersCollectionRef);

      setUsers(
        querySnapshot.docs.map((doc) => ({
          ...doc.data(), // スプレッド構文を使い、nameとageを取得
          id: doc.id, // document idを付与する
        }))
      );
    };
    getUsers();
  }, []);

  return (
    <div className="App">
      <input placeholder="Tom" onChange={(e) => setNewName(e.target.value)} />
      <input
        type="number"
        placeholder="30"
        onChange={(e) => setNewAge(e.target.value)}
      />
      <button onClick={createUser}>Create User</button>

      {users.map((user) => {
        return (
          <div key={user.id}>
            <p>Name: {user.name}</p>
            <p>Age: {user.age}</p>
          </div>
        );
      })}
    </div>
  );
}

export default App;

これにより以下のような画面になります。

Update

各userに年齢を+1するボタンを用意し、ボタンを押すとAgeに表示されている年齢が+1される処理が行われるようにします。

import {
  addDoc,
  collection,
  doc,
  getDocs,
  updateDoc,
} from "firebase/firestore";
import { useEffect, useState } from "react";
import "./App.css";
import { db } from "./firebase-config";

function App() {
  const [newName, setNewName] = useState("");
  const [newAge, setNewAge] = useState(0);
  const [users, setUsers] = useState([]);

  const usersCollectionRef = collection(db, "users");

  const createUser = async () => {
    await addDoc(usersCollectionRef, {
      name: newName,
      age: newAge,
    });
  };

  const updateUser = async (id, age) => {
    // 更新するdocumentをidをもとに取得
    const userDoc = doc(db, "users", id);
    const newFields = { age: age + 1 };
    await updateDoc(userDoc, newFields);
  };

  useEffect(() => {
    const getUsers = async () => {
      // getDocsを使用して、指定されたcollection内、ここではusersの全てのデータを取得する
      const querySnapshot = await getDocs(usersCollectionRef);

      setUsers(
        querySnapshot.docs.map((doc) => ({
          ...doc.data(), // スプレッド構文を使い、nameとageを取得
          id: doc.id, // document idを付与する
        }))
      );
    };
    getUsers();
  }, []);

  return (
    <div className="App">
      <input placeholder="Tom" onChange={(e) => setNewName(e.target.value)} />
      <input
        type="number"
        placeholder="30"
        onChange={(e) => setNewAge(e.target.value)}
      />
      <button onClick={createUser}>Create User</button>

      {users.map((user) => {
        return (
          <div key={user.id}>
            <p>Name: {user.name}</p>
            <p>Age: {user.age}</p>
            <button onClick={() => updateUser(user.id, user.age)}>
              age +1
            </button>
          </div>
        );
      })}
    </div>
  );
}

export default App;

Updateに関しては、こちらの公式のドキュメントの通りです。

1つのdocumentの参照は以下の記述で行うことができます。

const userDoc = doc(db, "users", id);

第二引数にはcollection名、第三引数にはdocument idを指定します。

公式ドキュメントはこちら

更新はupdateDocを使い、第一引数には上記で取得したdocumentを、第二引数には更新するプロパティを記述します。

Delete

最後に削除です。Deleteは一番簡単です。

import {
  addDoc,
  collection,
  deleteDoc,
  doc,
  getDocs,
  updateDoc,
} from "firebase/firestore";
import { useEffect, useState } from "react";
import "./App.css";
import { db } from "./firebase-config";

function App() {
  const [newName, setNewName] = useState("");
  const [newAge, setNewAge] = useState(0);
  const [users, setUsers] = useState([]);

  const usersCollectionRef = collection(db, "users");

  const createUser = async () => {
    await addDoc(usersCollectionRef, {
      name: newName,
      age: newAge,
    });
  };

  const updateUser = async (id, age) => {
    // 更新するdocumentをidをもとに取得
    const userDoc = doc(db, "users", id);
    const newFields = { age: age + 1 };
    await updateDoc(userDoc, newFields);
  };

  const deleteUser = async (id) => {
    // 削除するdocumentをidをもとに取得
    const userDoc = doc(db, "users", id);
    await deleteDoc(userDoc);
  };

  useEffect(() => {
    const getUsers = async () => {
      // getDocsを使用して、指定されたcollection内、ここではusersの全てのデータを取得する
      const querySnapshot = await getDocs(usersCollectionRef);

      setUsers(
        querySnapshot.docs.map((doc) => ({
          ...doc.data(), // スプレッド構文を使い、nameとageを取得
          id: doc.id, // documentidを付与する
        }))
      );
    };
    getUsers();
  }, []);

  return (
    <div className="App">
      <input placeholder="Tom" onChange={(e) => setNewName(e.target.value)} />
      <input
        type="number"
        placeholder="30"
        onChange={(e) => setNewAge(e.target.value)}
      />
      <button onClick={createUser}>Create User</button>

      {users.map((user) => {
        return (
          <div key={user.id}>
            <p>Name: {user.name}</p>
            <p>Age: {user.age}</p>
            <button onClick={() => updateUser(user.id, user.age)}>
              age +1
            </button>
            <button onClick={() => deleteUser(user.id)}>削除</button>
          </div>
        );
      })}
    </div>
  );
}

export default App;

documentを取得して、deleteDocでそのデータを指定するだけです。

公式のドキュメントがこちら

まとめ

CRUDのクエリのみを早見表として以下にまとめます。

Create
  const usersCollectionRef = collection(db, "users");
  const createUser = async () => {
    await addDoc(usersCollectionRef, {
      name: newName,
      age: newAge,
    });
  };

Read(全件取得)
    const getUsers = async () => {
      const querySnapshot = await getDocs(collection(db, "users"));
      setUsers(
        querySnapshot.docs.map((doc) => ({
          ...doc.data(),
          id: doc.id,
        }))
      );
    };

Update
  const updateUser = async (id, age) => {
    const userDoc = doc(db, "users", id);
    const newFields = { age: age + 1 };
    await updateDoc(userDoc, newFields);
  };

Delete
  const deleteUser = async (id) => {
    const userDoc = doc(db, "users", id);
    await deleteDoc(userDoc);
  };

以上、お読みいただきありがとうございました。

コメント