《React.js&Next.js超入門 第2版》6章をFirebase JavaScript SDK v9で動かす その１

[《React.js&Next.js超入門 第2版》](https://www.shuwasystem.co.jp/book/9784798063980.html "翔泳社のページ") 第6章のサンプルコードはFirebase JavaScript SDK v8 を用いて書かれています。
現在のバージョンは v9 であるため、そのままでは動きません。v9 で動くように修正します。

##6-3 JavaScriptからFirestoreを利用する
###リスト6-3
```JavaScript:components/fire.js
import { initializeApp } from 'firebase/app';

const firebaseConfig = {
  // 各プロジェクトの設定
  apiKey: "...",
  authDomain: "...",
  projectId: "...",
  storageBucket: "...",
  messagingSenderId: "...",
  appId: "..."
};

initializeApp(firebaseConfig);
```
v9ではimportの仕方が変わっています。
`import { initializeApp }`のように利用する関数を指定してimportします。

###リスト6-4
```JavaScript:pages/fire/index.js
import { useState, useEffect } from 'react';
import Layout from '../../components/layout';
import '../../components/fire';
import { getFirestore, collection, getDocs } from 'firebase/firestore/lite';

const db = getFirestore();

export default function Home() {
  const mydata = [];
  const [data, setData] = useState(mydata);
  const [message, setMessage] = useState('wait...');

  useEffect(() => {
    getDocs(collection(db, 'mydata')).then(snapshot => {
      snapshot.forEach(document => {
        const doc = document.data();
        mydata.push(
          <tr key={document.id}>
            <td><a href={'/fire/del?id=' + document.id}>{document.id}</a></td>
            <td>{doc.name}</td>
            <td>{doc.mail}</td>
            <td>{doc.age}</td>
          </tr>
        );
      });
      setData(mydata);
      setMessage('Firebase data.');
    });
  }, []);

  return <>
    <Layout header="Next.js" title="Top page.">
      <div className="alert alert-primary text-center">
        <h5 className="mb-4">{message}</h5>
        <table className="table bg-white text-left">
          <thead>
            <tr>
              <th>ID</th>
              <th>Name</th>
              <th>Mail</th>
            </tr>
          </thead>
          <tbody>
            {data}
          </tbody>
        </table>
      </div>
    </Layout>
  </>;
}
```
`const db = getFirestore();`でFirestoreにアクセスするためのインスタンスを取り出します。[^blank_parameter]
`collection(db, 'mydata')`でアクセスするコレクションを指定します。`getDocs()`でそのコレクションからデータを取得します。
`getDocs()`で取得したスナップショットに対する処理は、書籍に書いてある通りです。

###リスト6-5
```JavaScript:pages/fire/add.js
import { useState } from 'react';
import Layout from '../../components/layout';
import '../../components/fire';
import { getFirestore, collection, addDoc } from 'firebase/firestore/lite';
import { useRouter } from 'next/router';

const db = getFirestore();

export default function Add() {
  const [message] = useState('add data');
  const [name, setName] = useState('');
  const [mail, setMail] = useState('');
  const [age, setAge] = useState(0);
  const router = useRouter();

  const onChangeName = (e => {
    setName(e.target.value);
  });
  const onChangeMail = (e => {
    setMail(e.target.value);
  });
  const onChangeAge = (e => {
    setAge(e.target.value);
  });
  const doAction = (() => {
    const ob = {
      name: name,
      mail: mail,
      age: age
    };
    addDoc(collection(db, 'mydata'), ob).then(_ => {
      router.push('/fire');
    });
  });

  return <>
    <Layout header="Next.js" title="Top page.">
      <div className="alert alert-primary text-center">
        <h5 className="mb-4">{message}</h5>
        <div className="text-left">
          <div className="form-group">
            <label>Name:</label>
            <input type="text" onChange={onChangeName} className="form-control" />
          </div>
          <div className="form-group">
            <label>Mail:</label>
            <input type="text" onChange={onChangeMail} className="form-control" />
          </div>
          <div className="form-group">
            <label>Age:</label>
            <input type="number" onChange={onChangeAge} className="form-control" />
          </div>
        </div>
        <button onClick={doAction} className="btn btn-primary">
          Add
        </button>
      </div>
    </Layout>
  </>;
}
```
`addDoc(コレクションへの参照, ドキュメントデータ)`でコレクションにドキュメントを追加します。

###リスト6-6
```JavaScript:pages/fire/del.js
import { useState, useEffect } from 'react';
import Layout from '../../components/layout';
import '../../components/fire';
import { getFirestore, doc, getDoc, deleteDoc } from 'firebase/firestore/lite';
import { useRouter } from 'next/router';

const db = getFirestore();

export default function Delete() {
  const [message,setMessage] = useState('wait');
  const [data, setData] = useState(null);
  const router = useRouter();

  useEffect(() => {
    if (router.query.id != undefined) {
      setMessage('Delete id = ' + router.query.id);
      getDoc(doc(db, 'mydata', router.query.id)).then(ob => {
        setData(ob.data());
      });
    } else {
      setMessage(message +'.');
    }
  }, [message]);

  const doAction = () => {
    deleteDoc(doc(db, 'mydata', router.query.id)).then(_ => {
      router.push('/fire');
    });
  };

  return <>
    <Layout header="Next.js" title="Top page.">
      <div className="alert alert-primary text-center">
        <h5 className="mb-4">{message}</h5>
        <pre className="card p-3 m-3 h5 text-left">
          Name: {data != null ? data.name : '...'}<br />
          Mail: {data != null ? data.mail : '...'}<br />
          Age: {data != null ? data.age : '...'}
        </pre>
        <button onClick={doAction} className="btn btn-primary">
          Delete
        </button>
      </div>
    </Layout>
  </>;
}
```
`doc(Firestoreインスタンス, コレクション名, ドキュメントID)`でドキュメントが指定できます。`deleteDoc(指定したドキュメント)`でドキュメントを削除します。

###リスト6-7
```JavaScript:pages/fire/find.js
import { useState } from 'react';
import Layout from '../../components/layout';
import { getFirestore, collection, query, where, getDocs } from 'firebase/firestore/lite';
import '../../components/fire';

const db = getFirestore();

export default function Find() {
  const [message, setMessage] = useState('find data');
  const [find, setFind] = useState('');
  const [data, setData] = useState([]);
  const mydata = [];

  const onChangeFind = (e => {
    setFind(e.target.value);
  });

  const doAction = (() => {
    getDocs(query(collection(db, 'mydata'), where('name', '==', find))).then(snapshot => {
      snapshot.forEach(document => {
        const doc = document.data();
        mydata.push(
          <tr key={document.id}>
            <td><a href={'fire/del?id=' + document.id}>{document.id}</a></td>
            <td>{doc.name}</td>
            <td>{doc.mail}</td>
            <td>{doc.age}</td>
          </tr>
        );
      });
      setData(mydata);
      setMessage('find: ' + find);
    });
  });

  return <>
    <Layout header="Next.js" title="Top page.">
      <div className="alert alert-primary text-center">
        <h5 className="mb-4">{message}</h5>
        <div className="text-left">
          <div className="form-group">
            <label>Find:</label>
            <input type="text" onChange={onChangeFind} className="form-control" />
          </div>
        </div>
        <button onClick={doAction} className="btn btn-primary">
          Find
        </button>
      </div>
      <table className="table bg-white text-left">
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Mail</th>
            <th>Age</th>
          </tr>
        </thead>
        <tbody>
          {data}
        </tbody>
      </table>
    </Layout>
  </>;
}
```
`query(collection(db, 'mydata'), where('name', '==', find))`で、mydataコレクションから`name == find`を満たすドキュメントを返すクエリを作成します。
`getDocs()`でスナップショットを取り出します。
取り出したスナップショットに対する処理は書籍の通りです。

###入力テキストで始まるドキュメントの検索
```JavaScript:pages/fire/find.js
// importする関数からwhereを除き、orderBy, startAt, endAtを追加
import { getFirestore, collection, query, orderBy, startAt, endAt, getDocs } from 'firebase/firestore';

// orderBy, startAt, endAtを用いてクエリを作成
getDocs(query(collection(db, 'mydata'), orderBy('name'), startAt(find), endAt(find + '\uf8ff'))).then(snapshot => {
```
上記の2行を修正すると入力したテキストで始まる名前のドキュメントを表示します。

##参考
[React.js&Next.js超入門 第2版 サンプルコードのバグまとめ](https://qiita.com/SolKul/items/c2f03652267951419a2e)
[《React.js&Next.js超入門 第2版》6章をFirebase JavaScript SDK v9で動かす その２](https://qiita.com/sgigagaeru/items/b5a752f75986312fd6fb "")
[《React.js&Next.js超入門 第2版》6章をFirebase JavaScript SDK v9で動かす その３](https://qiita.com/sgigagaeru/items/d20cfb7331249620d290 "")

[^blank_parameter]: `getFirestore()`の引数を指定しなかった場合は、名前が`[DEFAULT]`のアプリ情報が渡されたと判断するようです。アプリの名前は`initializeApp`の第2引数で指定できます。指定しない場合は`[DEFAULT]`になります。