Firebaseのプロジェクトの作成から開始しユーザ情報を使ったReactのシンプルなアプリケーションを通してFirestoreデータベースのCreate, Read, Update, Delete(CRUD)の方法を学びことができます。CRUDに利用する関数の羅列だけではなく実際のコードをつかって利用方法を学ぶことができるのでより理解も早まります。またFirestoreを利用し始めた時に理解するのが少し難しいのではないかと思われるReference(参照)や Snapshotについても説明を行なっています。

動作確認にはFirebase SDK v9を利用しておりgetDocs, addDoc, updateDoc, deleteDoc, getDoc関数を使っています。

Reactプロジェクトの作成

Firebaseのプロジェクトを作成するために動作確認用のReactプロジェクトの作成を行います。プロジェクトの作成にはViteを利用しています。。npm init vite@latestを実行するとプロジェクト名と利用するフレームワーク、TypeScriptを利用するかどうか対話的に聞かれます。本文書ではプロジェクト名をreact-firebase、フレームワークにReactを選択してTypeScriptは利用しません。


 %  npm init vite@latest
✔ Project name: … vite-project
✔ Select a framework: › react
✔ Select a variant: › react

Scaffolding project in /Users/mac/Desktop/vite-project...

Done. Now run:

  cd vite-project
  npm install
  npm run dev

実行後作成したプロジェクトフォルダのvite-projectに移動してnpm installコマンドを実行してください。


% cd vite-project 
% npm install

npm installコマンド実行後にnpm run devコマンドを実行すると開発サーバが起動するのでブラウザからhttp://localhost:3000にアクセスしてください。


 % npm run dev

> vite-project@0.0.0 dev
> vite

Pre-bundling dependencies:
  react
  react-dom
  react/jsx-dev-runtime
(this will be run only when your dependencies or config have changed)

  vite v2.7.6 dev server running at:

  > Local: http://localhost:3000/
  > Network: use `--host` to expose

  ready in 502ms.

ブラウザ上には下記の画面が表示されます。動作確認用のReactプロジェクトの作成は完了です。

Reactの初期ページの表示
Reactの初期ページの表示

Firebaseのプロジェクトの作成

Googleアカウントにログインした状態でhttps://firebase.google.com/にアクセスします。Firebaseのトップページに”使ってみる”のボタンが表示されているのでボタンをクリックします。

firebaseのトップページ
firebaseのトップページ

Firebaseへようこその画面が表示されます。この画面からプロジェクトの作成を開始することができます。”プロジェクトの作成”ボタンをクリックしてください。

プロジェクト名をつける必要があるので任意の名前をつけてください。ここではmy-projectという名前にしています。名前を入力して”Firebaseの規約に同意します”にチェックを入れて”続行”ボタンをクリックします。」

プロジェクトの作成 名前をつける
プロジェクトの作成 名前をつける

Googleアナリティクスの設定画面が表示されますが動作確認のプロジェクトなので有効から非有効に変更します。変更を完了したら”プロジェクトを作成”ボタンをクリックしてください。

Googleアナリティクスの設定画面
Googleアナリティクスの設定画面

プロジェクトの作成画面が表示されます。しばらく時間がかかりますがプロジェクトの作成が完了すると準備画面が表示されます。続行ボタンをクリックしてください。

プロジェクトの準備完了画面
プロジェクトの準備完了画面

プロジェクトの概要画面が表示されます。my-projectという名前のプロジェクトが作成できました。

プロジェクトの概要画面
プロジェクトの概要画面

Firebaseを利用するためには利用するための設定情報とSDKが必要となります。Firebase内のデータへのCRUD(Create, Read, Update, Delete)についてFirebaseが準備しているライブラリを使って操作を行います。利用するためにはアプリの開始が必要なのでプロジェクトの概要画面の中央にある”</>”ボタンをクリックしてください。

プロジェクトだけではなくアプリにも名前をつける必要があります。こちらも任意の名前をつけてください。ここではmy-project-appという名前をつけてください。名前を入力したら”アプリを登録”ボタンをクリックしてください。

アプリの名前を登録
アプリの名前を登録

Firebase SDKの追加ということでfirebaseのライブラリのインストールと設定情報が表示されます。

Firebaseへの接続情報
Firebaseへの接続情報

npmコマンドを利用してfirebaseをインストールしてください。


 % npm install firebase

インストール完了後、package.jsonを確認するとインストールしたfirebaseのバージョンを確認することができます。本文書ではFireabse 9を利用するのでfirebaseのバージョンが9になっていることを確認してください。


//略
"dependencies": {
  "firebase": "^9.6.1",
  "react": "^17.0.2",
  "react-dom": "^17.0.2"
},
//略

ライブラリが完了後プロジェクトフォルダにfirebase.jsファイルを作成して表示されている接続情報をコピー&ペーストしてください。FirebaseのFirestore DatabaseとStorageを利用できるように設定を行っています。下記の情報は利用することはできないので各自が作成した設定情報をfirebase.jsファイルに設定してください。


import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { getStorage } from 'firebase/storage';

const firebaseConfig = {
  apiKey: 'AIzaSyBl6ETJSqdRDdKJ4u7l3uJXtS2KR6E0ic',
  authDomain: 'my-project-6fe6.firebaseapp.com',
  projectId: 'my-project-6fe6',
  storageBucket: 'my-project-6fe6.appspot.com',
  messagingSenderId: '95911302504',
  appId: '1:959113072504:web:580ab982e4a63a164428d',
};

const app = initializeApp(firebaseConfig);

export const db = getFirestore();
export const storage = getStorage();

export default app;

設定が完了したら”コンソールに進む”ボタンをクリックしてください。次はCloud Firestoreのデータベースを作成するのでCloud Firestoreのボタンをクリックしてください。

プロジェクトの概要画面表示
プロジェクトの概要画面表示

Cloud Firestoreのデータベースの作成

Cloud Firestoreの画面からデータベースの作成を行うために”データベースの作成”ボタンをクリックしてください。

Cloud Firestoreの画面
Cloud Firestoreの画面

データベースの作成画面が表示されますが動作確認なので”テストモードで開始する”を選択して”次へ”ボタンをクリックしてください。

データベースの作成画面
データベースの作成画面

データベースを作成する場所を選択することができるので東京付近であればasia-northeast1, 大阪付近であればasia-northeast2を選択します。選択したら”有効にする”ボタンをクリックします。

データベースを作成するロケーションの設定
データベースを作成するロケーションの設定

データベースの作成が完了すると以下の画面が表示されます。

データベースの画面
データベースの画面

データの保存

一般的に利用されているMySQLなどのSQLデータベースではデータベースを作成後テーブルを作成してテーブルの列にデータを挿入していくことになりますがFirestoreではドキュメントの中にデータを保存していきます。ドキュメントをまとめるものとしてコレクションがあります。コレクションは名前のついたドキュメントの入れ物だと考えることができます。

SQLデータベースのようにテーブルを作成する際に列名、列のタイプなどすべて指定して作成を行いますがFirestoreではコレクションはドキュメントの入れ物なのでそのような設定を行うことはありません。
fukidashi
コレクションとドキュメントの関係
コレクションとドキュメントの関係

コレクションを作成していきますがコレクションは入れ物なので作成する際にはドキュメントを作成する必要があります。ドキュメントを作成する際はどのコレクションの中に入れるのか必ず指定する必要があります。

ブラウザ上からドキュメントにデータを保存することが可能なので最初は管理画面上からデータ保存を行います。ドキュメントへのデータの保存には入れ物であるコレクションの名前を設定する必要があるので”コレクションを開始”をクリックします。

コレクションの作成開始
コレクションの作成開始

コレクションのIDである名前を設定します。これから作成したコレクションにユーザ情報を保存していくためにusersというIDにしています。コレクションIDを入力したら”次へ”ボタンをクリックしてください。

コレクションの名前設定
コレクションの名前設定

usersコレクションの中にドキュメントを作成しその中にデータを保存していきます。コレクションに名前をつけて終了ということはなくコレクションにはドキュメントが必要なのでコレクションを作成した時には必ずドキュメントを作成する必要があります。

ドキュメントにもIDを設定する必要があります。任意の名前をつけることもできますし、自動でIDを振ることもできます。自動でIDをクリックするとアルファベットと数字で組み合わさったランダムな文字列が表示されます。

ドキュメントの作成
ドキュメントの作成

ドキュメントのIDを設定したらフィールド名とタイプ、その値を設定する必要があります。

フィールドの設定
フィールドの設定

ユーザ情報の保存するため本文書ではフィールドにadmin, name, emailを設定します。

フォールド名にadminを入力してselectメニューからタイプを選択します。タイプにはstring, numberなどいろいろなものがあります。arrayを選択すると配列、mapを選択するとオブジェクトを保存することができます。

タイプの選択
タイプの選択

adminにはboolean, nameとemailにはstringを設定し各フィールドに値を設定して保存します。

ユーザに関するフィールドの設定
ユーザに関するフィールドの設定

これでFirestoreにデータを保存することができました。

データの保存
データの保存

もう一人別のユーザ情報の登録を行います。既存のコレクションにドキュメントを追加する場合は”コレクションを開始”ではなく”ドキュメントを追加”をクリックします。コレクションという入れ物にドキュメントを追加していきます。

SQLデータベースでは列が決められているので自由に列を増やすことはできませんがFirestoreではドキュメントごとに異なるフィールドやタイプを設定することができます。下記では最初のユーザにはなかったフィールドageを追加しています。

ドキュメントの追加
ドキュメントの追加

保存を押すとドキュメントが追加されます。同じコレクションに入っているドキュメントでも異なるフィールドを持っています。

コレクションの追加
コレクションの追加

データ削除

管理画面上からコレクションを削除したい場合はusersの横にある縦3つの丸のアイコンをクリックすることで行えます。

コレクションの削除
コレクションの削除

削除の確認画面が表示されるので削除したいコレクションのIDを入力する必要があります。

コレクションの削除確認画面
コレクションの削除確認画面

ドキュメントを削除したい場合はドキュメントIDの横に表示されている縦3つの丸のアイコンをクリックすることで行うことができます。

フィールドの更新と削除はフィールドにアイコンを載せると鉛筆、ゴミ箱のアイコンが表示されるのでクリックすることで更新画面または削除を行うことができます。

フィールドの更新/削除
フィールドの更新/削除

管理画面からのFirestoreの操作方法が理解できたので次は実際にコードを利用して操作を行なっていきます。

ReactからのFirestoreの操作

ユーザ一覧の取得

Firestoreのデータベースに保存したusersコレクションのドキュメントからユーザ情報の一覧を取得します。App.jsxを下記のように更新します。取得したデータはusersに保存し、データの取得はuseEffectの中で行います。


import { useState, useEffect } from 'react';

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

  useEffect(() => {

  }, []);

  return <div></div>;
}

export default App;

データはドキュメントに含まれていますがドキュメントが入ったusersコレクションからデータを取得したいので最初にusersコレクションの参照を取得します。参照しているだけなのでドキュメントのデータを取得しているわけではありません。Firestoreで参照の取得という形で処理を行いたいコレクションやドキュメントを処理前に指定します。参照は英語でReferenceという単語なのでわかりやすいように参照を保存した変数にはRefという名前をつけています。


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

コレクションの参照に利用しているcollection関数はfirebase/firestore, Firestoreに接続するための情報が含まれたdbはfirebase.jsからimportを行います。取得した参照には何が入っているか気になると思いますのでconsole.logでコンソールに表示してみましょう。


import { useState, useEffect } from 'react';
import { db } from '../firebase';
import { collection } from 'firebase/firestore';

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

  useEffect(() => {
    const usersCollectionRef = collection(db, 'users');
    console.log(usersCollectionRef);
  }, []);

  return <div></div>;
}

export default App;
firestoreで利用する関数はすべてimportを行う必要があります。importをおこなっていない場合には処理が行われずエラーになるのでその場合はimport処理を行ってください。
fukidashi

参照の中身にはさまざまな情報が入っており正直これを見ただけで何に利用するのかよくわからないとは思いますがtypeがcollectionであることやid, pathがusersに設定されていることがわかります。

collectionの参照の中身
collectionの参照の中身

コレクションの参照からコレクションの中にあるドキュメントを取得するためにgetDocs関数を利用します。コレクションには通常複数のドキュメントが入っているので関数には複数形の”s”が付いています。後ほど”s”のないgetDoc関数もありますで間違えないようにしましょう。

getDocs関数の引数には事前に取得したコレクションの参照を指定します。getDocsによりQuery(クエリー)を実行することができます。クエリーを実行するとその結果としてPromiseでQuerySnapshotが戻されます。QuerySnapshotという名前が最初はわかりずらいと思いますがgetDocs関数で実行したQueryの結果が含まれるオブジェクトです。ドキュメントのデータのみ入っているわけではありませんがSnapshotという名前はクエリーを実行したその時点でのデータ全体を取り出しているのでQuerySnapshotがついてると考えればいいかと思います。QuerySnapshotの中にはさまざまな情報が含まれていますがその中の一つにドキュメントのデータを含むQueryDocumentSnapshotがあります。QuerySnapshotの中身を見てみましょう。


useEffect(() => {
  const usersCollectionRef = collection(db, 'users');
  getDocs(usersCollectionRef).then((querySnapshot) => {
    console.log(querySnapshot);
  });
}, []);

上記のコードを実行するとQuerySnapshotの中身がコンソールに表示されます。その中にdocsという文字列を見つけることができますがその配列の中にQueryDocumentSnapshotが入っています。名前からクエリーを実行した時点でのドキュメントのデータが含まれていると予想することができます。

querySnapshotの中身
QquerySnapshotの中身

QuerySnapShotの中にあるdocsは配列なのでforEachで展開してdoc(QueryDocumentSnapshot)のみ取り出すことができます。取り出したdocの中身も見てみましょう。


useEffect(() => {
  const usersCollectionRef = collection(db, 'users');
  getDocs(usersCollectionRef).then((querySnapshot) => {
    querySnapshot.docs.forEach((doc) => console.log(doc));
  });
}, []);

docの中身を見るとドキュメントのidを確認することができ、_documentの中にデータが入っています。idの値がドキュメントのIDと一致するかはFirebaseの管理画面から確認することができます。

docの中身を確認
docの中身を確認
docのデータ構造からdocのidを取得したい場合はdoc.idで取得できることがわかります。
fukidashi

_documentをコンソール上で展開していくとfields(フィールド)がありその中に先程保存したユーザの情報を確認することができます。

_documentにデータが含まれている
_documentにデータが含まれている

ネスト化された上記のデータ構造を見てどのようにフィールドのデータのみ取得することができるのか気になる人もいるのではないでしょうか。データのみの取得法方法はシンプルでdocのQueryDocumentSnapshotはdocメソッドを持っているためdoc.data()で中身のデータを取り出すことができます。


getDocs(usersCollectionRef).then((querySnapshot) => {
  querySnapshot.docs.forEach((doc) => console.log(doc.data()));
});
dataメソッドでフィールドと値を取得
dataメソッドでフィールドと値を取得
Firestoreのドキュメントの中ではQuerySnapshotからdocを取り出すのにquerySnapshot.docs.forEachではなくquerySnapshot.forEachと記載されています。querySnapshotはforEachメソッドを持っているためにdocsを省略してもforEachを実行してdocを取り出すことができます。
fukidashi

ここまでの一連の説明でFirestoreに保存されているusersコレクションの中のドキュメントのデータは参照を利用したgetDocs関数によるクエリーによってQuerySnapshotを取得し、QuerySnapshotのdocsの展開とdata関数によってドキュメントのデータを取り出すことができることがわかりました。

ドキュメントデータの表示

取得したデータをuseStateで定義したusersに保存してブラウザ上に表示させてみましょう。forEachは配列を展開して繰り返すだけの関数なので戻り値はありません。setUsersでusersにユーザ情報を保存するために展開したdoc.data()を配列にする必要があります。そのためここではforEachではくmap関数を利用します。


useEffect(() => {
  const usersCollectionRef = collection(db, 'users');
  getDocs(usersCollectionRef).then((querySnapshot) => {
    setUsers(querySnapshot.docs.map((doc) => doc.data()));
  });
}, []);

これで配列としてusersに保存することができます。ブラウザ上でusersを展開する際にkey propsで一意なキーを設定する必要があります。ドキュメントの中にはemailのように一意となりうる値もありますがドキュメントのidを利用するためmap関数の処理を変更します。

ドキュメントのidをdoc.data()の結果とマージするため以下のように記述します。


querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id }))

これでidが含まれたオブジェクトとなります。保存されたusersをブラウザ上に表示させるためmap関数で展開します。


import { useState, useEffect } from 'react';
import { db } from '../firebase';
import { collection, getDocs } from 'firebase/firestore';

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

  useEffect(() => {
    const usersCollectionRef = collection(db, 'users');
    getDocs(usersCollectionRef).then((querySnapshot) => {
      setUsers(
        querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id }))
      );
    });
  }, []);

  return (
    <div>
      {users.map((user) => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

export default App;

ブラウザ上にユーザ名が表示されます。Firestoreに保存されているデータをブラウザ上に表示できるようになりました。

ユーザ一覧表示
ユーザ一覧表示

ここまでの動作確認でFirestoreではドキュメントが保存されているコレクションの場所を参照(Reference)によって指定し、参照を利用してクエリーを実行することで実行した時点でのスナップショットデータ(Snapshot)を取得しているという流れを理解することでReference、Snapshotが何なのかイメージできるようになってきたかと思います。Snapshotには複数のドキュメントを含むSnapshotであるQuerySnapshotと単一のドキュメントのデータを含むSnapshotであるQueryDocumentSnapshotがあることがわかりました。またどちらもクエリーを実行した結果、取得できるものなので頭にQueryがついていると考えることができます。ここではgetDocsの引数にコレクションの参照を設定しているだけですが一度に取得するドキュメントの数を指定(limit)したり、取得する順番を指定(orderBy)したり、検索条件(where)を指定したりと複雑なクエリーを設定することができます。

個別のユーザ情報を取得

ドキュメントのidがわかっている場合はcollection関数ではなくdoc関数を利用することでドキュメントのデータを取得することができます。collection関数を利用した場合は複数のdocを含むquerySnapshotを取得しましたがdoc関数を利用する場合はDocumentSnapshotが戻されます。querySnapshotの中にQueryDocumentSnapshotが出てきましたがQueryDocumentSnapshotはDocumentSnapshotを継承しておりどちらもdataメソッド中のデータを取得することができます。

idはH4Tjx8FaXpG8rIz3eVtcを利用します。ドキュメントのIDなのでFirebaseの管理画面上からも確認することができます。ここではquerySnapshotのforEachの展開時に確認した値です。

doc関数では、引数にコレクションの名前とドキュメントのidを指定します。実行するとドキュメントの参照を取得することができるのでgetDoc関数に取得した参照の情報を設定するとDocumentSnapshotがpromiseで戻されます。


const userDocumentRef = doc(db, 'users', 'H4Tjx8FaXpG8rIz3eVtc');
getDoc(userDocumentRef).then((documentSnapshot) => {
  console.log(documentSnapshot);
});
1つのデータしか戻らないのでgetDocsの複数系を意味するsはつかずgetDocとなります。Firestoreのドキュメントでは変数名のdocumentSnapshotを略してdocSnapと記述しています
fukidashi

DocumentSnapshotの中に含まれるデータを取得したい場合にはdataメソッドを利用することができます。


const userDocumentRef = doc(db, 'users', 'H4Tjx8FaXpG8rIz3eVtc');
getDoc(userDocumentRef).then((documentSnapshot) => {
  console.log(documentSnapshot.data());
});
//取得できるデータ
{name: 'John Doe', admin: true, email: 'john@test.com'}

getDocsでは戻される値がquerySnapshotでその中にqueryDocumentSnapshotが含まれているためデータを取り出すためにmap関数を利用しましたがgetDocではDocumentSnapshotが戻されmap関数などの処理は必要なくdataメソッドで中のデータを取得することができます。

もしドキュメントのIDが誤っていたりデータが削除された場合はDocumentSnapshotにデータ含まれているかチェックが必要となる場合はあります。その際にはexistsメソッドを利用することができます。データがある場合はtrue,ない場合にはfalseが戻されます。existsメソッドはDocumentSnapshotが持っているメソッドです。


const userDocumentRef = doc(db, 'users', 'H4Tjx8FaXpG8rIz3eVtc');
getDoc(userDocumentRef).then((documentSnapshot) => {
  if (documentSnapshot.exists()) {
    console.log('Document data:', documentSnapshot.data());
  } else {
    console.log('No such document!');
  }
DocumentSnapshotは参照先にデータがない場合もありますがFirestoreへのクエリーの結果として取得されたQueryDocumentSnapshotにはデータが含まれていないということはありません。QueryDocumentSnapshotにはデータが含まれていないことはありませんがQuerySnapshotにはQueryDocumentSnapshotが全く含まれていないということはありません。クエリーの条件に一致するドキュメントがない場合など。
fukidashi

リアルタイムでのデータ取得

Firestoreにはリアルタイムでデータを取得する機能を持っています。新たなユーザ追加された場合に表示されたユーザ一覧に登録したユーザ情報がリアルタイムで追加させるといったことが可能になります。

リアルタイムでデータを取得したい場合はonSnapshot関数を利用します。onSnspshotを実行するとUnSubscribe関数が戻されます。データの更新を検知するためにリスナーの登録を行なっているのでアンマウント時にリスナーの削除を行うためUnSubscribe関数を実行しています。


const usersCollectionRef = collection(db, 'users');
const unsub = onSnapshot(usersCollectionRef, (querySnapshot) => {
  setUsers(
    querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id }))
  );
});
return unsub;
}, []);

ユーザを追加する機能をReactのアプリケーション上に追加していないのでFirebaseの管理画面からドキュメントの追加を行うとリアルタイムでユーザ情報が追加されることを確認することができます。

getDocsでユーザの登録を行なっていもリアルタイムでユーザ情報が追加されることはありません。
fukidashi

先程はonSnapshot関数の第一引数にはcollectionの参照を設定しましたがドキュメントの参照も設定することができます。


useEffect(() => {
  const userDocumentRef = doc(db, 'users', 'H4Tjx8FaXpG8rIz3eVtc');
  const unsub = onSnapshot(userDocumentRef, (documentSnapshot) => {
    console.log(documentSnapshot.data());
  });
  return unsub;
}, []);

onSnapshot実行中にFirebaaseの管理画面からnameの変更やフィールドの追加を行うと変更を検知してコンソールにドキュメントの情報が表示されます。

ここまでの動作確認onSnapshot関数を利用することでドキュメントのリアルタイムの変更を検知/反映できることが確認できました。

データの追加

入力フォームの追加

ブラウザ上からユーザ情報を入力するために入力フォームを作成する必要があります。ユーザのnameとemailが入力できる簡易的なフォームを作成します。


<form onSubmit={handleSubmit}>
  <div>
    <label>名前</label>
    <input name="name" type="text" placeholder="名前" />
  </div>
  <div>
    <label>メールアドレス</label>
    <input name="email" type="email" placeholder="メールアドレス" />
  </div>
  <div>
    <button>登録</button>
  </div>
</form>

登録ボタンをクリックするとhandleSubmitメソッドが実行されます。handleSubmitメソッドの中でeventからinput要素で入力したnameとemailを取得しています。


const handleSubmit = async (event) => {
  event.preventDefault();
  const { name, email } = event.target.elements;
  console.log(name.value,email.value)
};

入力フォームを加えたコードは以下のようになります。


import { useState, useEffect } from 'react';
import { db } from '../firebase';
import { collection, onSnapshot } from 'firebase/firestore';

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

  useEffect(() => {
    const usersCollectionRef = collection(db, 'users');
    const unsub = onSnapshot(usersCollectionRef, (querySnapshot) => {
      setUsers(
        querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id }))
      );
    });
    return unsub;
  }, []);

  const handleSubmit = async (event) => {
    event.preventDefault();
    const { name, email } = event.target.elements;
    console.log(name.value, email.value);
  };

  return (
    <div style={{ margin: '50px' }}>
      <form onSubmit={handleSubmit}>
        <div>
          <label>名前</label>
          <input name="name" type="text" placeholder="名前" />
        </div>
        <div>
          <label>メールアドレス</label>
          <input name="email" type="email" placeholder="メールアドレス" />
        </div>
        <div>
          <button>登録</button>
        </div>
      </form>
      <h1>ユーザ一覧</h1>
      <div>
        {users.map((user) => (
          <div key={user.id}>{user.name}</div>
        ))}
      </div>
    </div>
  );
}

export default App;
入力フォームを追加
入力フォームを追加

名前とメールアドレスを入力して登録ボタンをクリックするとブラウザのデベロッパーツールのコンソールに入力した名前とメールアドレスが表示されることを確認してください。

addDoc関数によるドキュメント追加

入力した名前とメールアドレスをFirestoreのデータベースのドキュメントに追加できるようにhandleSubmitメソッドを更新します。

ドキュメントを追加する場合も参照を取得する必要があります。usersコレクションのドキュメントを追加するのでcollection関数を利用して参照を取得します。ドキュメントの追加にはaddDoc関数を使い、第一引数にコレクションの参照、第2引数にはフィールドのキーと値を持ったオブジェクトを指定します。値には入力したnameとemailを設定しています。addDocを実行するとDocumentReferenceが戻されるのでconsole.logで中身を確認できるようにしています。


const handleSubmit = async (event) => {
  event.preventDefault();
  const { name, email } = event.target.elements;
  console.log(name.value, email.value);
  const usersCollectionRef = collection(db, 'users');
  const documentRef = await addDoc(usersCollectionRef, {
    name: name.value,
    email: email.value,
  });
  console.log(documentRef);
};

名前とメールアドレスを入力して登録ボタンをクリックするとonSnapshotによりドキュメントの追加を検知して即座にユーザ一覧に登録したユーザ情報が表示されます。

ユーザの追加後の画面
ユーザの追加後の画面

コンソールに表示されるDocumentReferenceにはどのような情報が入っているか確認しておきます。登録したドキュメントのIDが含まれていることがわかります。IDはFirestoreによって自動で設定されます。Referenceという名前通り参照という意味を持つので追加したドキュメントのデータが含まれているわけではないことがわかります。

DocumentReferenceの中身
DocumentReferenceの中身

IDを利用して参照を取得してgetDocを使うことで追加したドキュメントを取得することができます。

setDoc関数によるドキュメント追加

ドキュメントする方法にはaddDoc関数の他にsetDoc関数を使った方法があります。違いはドキュメントのIDを自動で設定するか指定しないかです。Firebaseの管理画面上でドキュメントを登録する際にIDを手動で設定できたようにsetDocを利用することで任意の名前のIDを設定することができます。

setDocを利用する場合はdoc関数を利用して第3引数にIDを指定します。


  const handleSubmit = async (event) => {
    event.preventDefault();

    const userDocumentRef = doc(db, 'users', 'ABCDEF');
    const documentRef = await setDoc(userDocumentRef, {
      name: name.value,
      email: email.value,
    });

    console.log(documentRef);
  };

nameとemailを入力後登録ボタンを押すと入力したユーザの名前がユーザ一覧に即座に追加されます。コンソールを見るとaddDocではDocumentReferenceが表示されましたがsetDocではDocumentReferenceは戻されずundefinedと表示されます。setDocではaddDocとは異なり何も戻されないことがわかりました。追加したドキュメントの情報を取得したい場合はdoc関数で指定したドキュメントのIDを利用して取得することができます。

Firebaseの管理画面を確認するとdoc関数の引数で指定したABCDEFをIDに持つドキュメントが表示されることが確認できます。

指定したドキュメントIDの確認
指定したドキュメントIDの確認

実際のコードを利用してaddDocとsetDocの違いを理解することができました。

setDocでも自動で設定されたIDを利用することができます。その場合は事前に参照を取得してその後にsetDocでドキュメントを登録することで実現できます。


const handleSubmit = async (event) => {
  event.preventDefault();
  const { name, email } = event.target.elements;

  const userDocumentRef = doc(collection(db, 'users'));
  await setDoc(userDocumentRef, {
    name: name.value,
    email: email.value,
  });
};

この場合はdoc関数を実行した時点でuserDocumentRefにIDが作成されることになります。追加されたドキュメントのIDを取得したい場合はuserDocumentRef.idから取得することができます。

タイムスタンプの追加

ドキュメントを追加する際に追加した時刻のタイムスタンプを一緒に登録したい場合serverTimeStamp関数を利用することができます。設定方法は簡単で下記のようにtimestamp フィールドを追加してserverTimeStampを指定するだけです。


await setDoc(userDocumentRef, {
  name: name.value,
  email: email.value,
  timpstamp: serverTimestamp(),
});

ユーザの追加を行い管理画面で確認するとフィールドにtimestampを持つドキュメントが追加されていることが確認できます。

管理画面で時刻の設定確認
管理画面で時刻の設定確認

ドキュメントの削除

ドキュメントを削除する場合も参照を利用して削除を行います。

IDを利用した場合

削除機能を追加するためにユーザ一覧に削除ボタンを追加します。ボタンにはonClickイベントを追加します。


<div>
  {users.map((user) => (
    <div key={user.id}>
      <span>{user.name}</span>
      <button onClick={() => deleteUser(user.id)}>削除</button>
    </div>
  ))}
</div>

onClickイベントで指定したdeleteUser関数を追加します。deleteUserの引数にはドキュメントのIDが入っています。doc関数を使って参照を取得します。deleteDoc関数に参照を指定するだけで参照したドキュメントを削除することができます。


const deleteUser = async (id) => {
  const userDocumentRef = doc(db, 'users', id);
  await deleteDoc(userDocumentRef);
};

ユーザの右側に表示されている削除ボタンをクリックするとユーザ一覧から削除ボタンを押したユーザが消えます。

where句を利用した場合

IDを利用してdoc関数で参照を取得して削除を行うことができましたがもしIDがわからない場合の削除方法を確認しておきます。IDがわからないがnameがわかっている時はクエリーの検索条件をwhereで指定してquerySnapshotを取得しさらに展開してQueryDocumentSnapshotからIDを取り出すことで削除することができます。

where句を利用した削除を実現するためにonClickイベントで実行するdelete関数の引数をuser.idからuser.nameに変更します。


<button onClick={() => deleteUser(user.name)}>削除</button>

nameを持つドキュメントを見つける必要があるためquery関数の中でwhere句を利用してnameフィールドの値がname(ボタンをクリックしたユーザの名前が入る)と一致するものを指定してします。getDocs関数の引数にquery関数の戻り値であるqを指定、クエリーを実行します。querySnapshotには検索条件に一致したドキュメントを含んでいます。querySnapshotをforEach関数で展開してdocument.idを取得して参照を取得して削除を行っています。今回はボタンを押した一人のユーザのみ削除ですが下記の方法では同じ名前を持つユーザが複数いれば一括で削除することが可能になります。


const deleteUser = async (name) => {
  const userCollectionRef = collection(db, 'users');
  const q = query(userCollectionRef, where('name', '==', name));
  const querySnapshot = await getDocs(q);
  querySnapshot.forEach(async (document) => {
    const userDocumentRef = doc(db, 'users', document.id);
    await deleteDoc(userDocumentRef);
  });
};

ドキュメントの更新

ドキュメントを削除する場合も参照を利用して更新を行います。

フィールドのadminがtrueの場合のみadminボタンを表示させボタンをクリックするとadminがtrueになる設定を行います。adminがtrueの場合はボタンを表示させないように設定を行っています。ボタンにocClickイベントを追加しchangeAdmin関数を設定しています。


<div>
  {users.map((user) => (
    <div key={user.id}>
      <span>{user.name}</span>
      <button onClick={() => deleteUser(user.id)}>削除</button>
      {!user.admin && (
        <button onClick={() => changeAdmin(user.id)}>admin</button>
      )}
    </div>
  ))}
</div>

changeAdmin関数を追加しドキュメントの更新を行います。更新したいドキュメントの参照を取得してupdateDoc関数の第一引数に参照を設定し、第二引数に更新を行うフィールドと値を持つオブジェクトを設定します。


const changeAdmin = async (id) => {
  const userDocumentRef = doc(db, 'users', id);
  await updateDoc(userDocumentRef, {
    admin: true,
  });
};
更新用のadminボタンを追加
更新用のadminボタンを追加

adminボタンを押すとchangeAdmin関数が実行されupdateDocによりフィールドadminの値がtrueになるのでボタンを押したユーザのadminボタンは非表示になります。

ここまでの動作確認でFirestoreに保存されているデータに対するCRUD(Create, Read, Update, Delete)の方法を理解することができました。

その他の操作

並び替え

ユーザ情報一覧を見ると表示されているユーザの情報は作成順でもなければアルファベット順でもなくIDの値によって昇順に並んでいます。IDではなくフィールドの値を利用して並び替えを行う方法を確認します。

ユーザ情報の中のnameフィールドを利用して並び替えを行います。並び替えにはquery関数とorderBy関数を利用します。どちらもfirebasae/firestoreからimportする必要があります。


const usersCollectionRef = collection(db, 'users');
const q = query(usersCollectionRef, orderBy('name'));
const unsub = onSnapshot(q, (querySnapshot) => {
  setUsers(
    querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id }))
  );
});

降順に並び替えたい場合にはorderByの第二引数にdecsを設定します。


const q = query(usersCollectionRef, orderBy('name', 'desc'));

取得件数

取得件数を指定したい場合にはlimit関数を利用します。nameフィールドの値を元に並び替えられた後の2件のドキュメントを取得することができます。


const q = query(usersCollectionRef, orderBy('name'), limit(2));

クエリー

deleteDocで削除を行う際にwhere関数に”==”を使うことで検索を行う方法を確認しました。”==”以外にもクエリー演算子には以下のようなものがあります。

  • <
  • <=
  • ==
  • >
  • >=
  • !=
  • array-contains
  • array-contans-any
  • in
  • not-in

adminのユーザのみ取得した場合には以下のようにwhere関数と”==”を利用してクエリーを実行することができます。


const q = query(usersCollectionRef, where('admin', '==', true));