《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-5 メッセージが送れるアドレスブック
書籍の中に記載はありませんが、以下の構造のデータをあらかじめFirebaseに作成するようです。

```
> コレクション 'address'
  > ドキュメント 'ログインユーザのメールアドレスがキー'
    > コレクション 'address' (アドレス帳が格納される)
      > ドキュメント 'アドレス帳に登録されるユーザのメールアドレスがキー'
        > フィールド flag, mail, memo, name, tel (登録されたユーザの情報)
        > コレクション 'message' (ユーザとのメッセージが格納される)
          > ドキュメント 'キーは自動生成'
            > フィールド comment, time (メッセージの情報)
```
###リスト6-11
```JavaScript:pagegs/address/index.js
import { useState, useEffect } from 'react';
import Layout from '../../components/layout';
import { getAuth, GoogleAuthProvider, signOut, signInWithPopup } from 'firebase/auth';
import { getFirestore, collection, getDocs } from 'firebase/firestore/lite';
import { useRouter } from 'next/router';
import '../../components/fire';

const db = getFirestore();
const auth = getAuth();
const provider = new GoogleAuthProvider();

signOut(auth);

export default function Index() {
  let addresses = [];
  const [user, setUser] = useState(null);
  const [data, setData] = useState(addresses);
  const [message, setMessage] = useState('please login...');
  const router = useRouter();

  const login = () => {
    signInWithPopup(auth, provider).then(result => {
      setUser(result.user.displayName);
      setMessage('logined: ' + result.user.displayName);
    }).catch(_ => {
      setUser('NONE');
      setMessage('not logined.');
    });
  };

  const logout = () => {
    signOut(auth);
    setUser(null);
    addresses = [];
    setData(addresses);
    setMessage('logout');
  };

  const doLogin = _ => {
    if (auth.currentUser == null) {
      login();
    } else {
      logout();
    }
  };

  const doAction = _ => {
    router.push('/address/add');
  };

  const doLink = e => {
    const id = e.target.id;
    router.push('/address/info?id=' + id);
  };

  useEffect(() => {
    if (auth.currentUser != null) {
      setUser(auth.currentUser.displayName);
      setMessage(auth.currentUser.displayName + 'さんの登録アドレス');
      getDocs(collection(db, 'address', auth.currentUser.email, 'address')).then(snapshot => {
        snapshot.forEach(document => {
          const doc = document.data();
          addresses.push(
            <li className="list-group-item list-group-item-action p-1" onClick={doLink} key={document.id} id={document.id}>
              {doc.flag ? '√' : ''}{doc.name} ({doc.mail})
            </li>
          );
        });
        setData(addresses);
      });
    } else {
      addresses.push(
        <li key="1">can&apos;t get data.</li>
      );
    }
  }, [message]);

  return <>
    <Layout header="Next.js" title="Address book.">
      <div className="alert alert-primary text-center">
        <h6 className="text-right" onClick={doLogin}>
          LOGINED: {user}
        </h6>
        <h5 className="mb-4">{message}</h5>
        <ul className="list-group">
          {data}
        </ul>
        <hr />
        <button className="btn btn-primary" onClick={doAction}>Add address</button>
      </div>
    </Layout>
  </>;
}
```
`getDocs(collection(db, 'address', auth.currentUser.email, 'address'))`で表示するスナップショットを取得しています。
`collection(db, 'address', auth.currentUser.email, 'address')`で以下のアドレス帳が格納されるコレクションへの参照を取り出しています。[^datamodel]

```
> コレクション 'address'
  > ドキュメント 'auth.currentUser.emailがキー'
    > コレクション 'address' (アドレス帳が格納される)
```
取り出したコレクションへの参照から`getDocs()`でスナップショットを取り出しています。
コレクションへの参照は`collection(db, 'address/' + auth.currentUser.email + '/address')`とパスで指定することもできます。

###リスト6-12
```JavaScript:pages/address/add.js
import { useState, useEffect } from 'react';
import Layout from '../../components/layout';
import { getFirestore, setDoc, doc } from 'firebase/firestore/lite';
import { getAuth } from 'firebase/auth';
import { useRouter } from 'next/router';
import '../../components/fire';

const db = getFirestore();
const auth = getAuth();

export default function Add() {
  const [message] = useState('add address');
  const [name, setName] = useState('');
  const [mail, setMail] = useState('');
  const [tel, setTel] = useState('');
  const [memo, setMemo] = useState('');
  const router = useRouter();

  useEffect(() => {
    if (auth.currentUser == null) {
      router.push('/address');
    }
  }, []);

  const onChangeName = (e => {
    setName(e.target.value);
  });
  const onChangeMail = (e => {
    setMail(e.target.value);
  });
  const onChangeTel =(e => {
    setTel(e.target.value);
  });
  const onChangeMemo = (e => {
    setMemo(e.target.value);
  });

  const doAction = (_ => {
    const ob = {
      name: name,
      mail: mail,
      tel: tel,
      memo: memo,
      flag: false
    };
    setDoc(doc(db, 'address', auth.currentUser.email, 'address', mail), ob).then(_ => {
      router.push('/address');
    });
  });

  const goBack = _ => {
    router.push('/address');
  };

  return <>
    <Layout header="Next.js" title="Create data.">
      <div className="alert alert-primary text-center">
        <h5 className="mb-4">{message}</h5>
        <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>Tel:</label>
          <input type="text" onChange={onChangeTel} className="form-control" />
        </div>
        <div className="form-group">
          <label>Memo:</label>
          <input type="text" onChange={onChangeMemo} className="form-control" />
        </div>
      </div>
      <button onClick={doAction} className="btn btn-primary">
        Add
      </button>
      <button onClick={goBack} className="btn">
        Go Back
      </button>
    </Layout>
  </>;
}
```
`setDoc(doc(db, 'address', auth.currentUser.email, 'address', mail), ob)`でドキュメントに値を設定しています。
`doc(db, 'address', auth.currentUser.email, 'address', mail)`で以下のドキュメントへの参照を取り出しています。[^datamodel]

```
> コレクション 'address'
  > ドキュメント 'auth.currentUser.emailがキー'
    > コレクション 'address' (アドレス帳が格納される)
      > ドキュメント 'mailがキー'
```
取り出した参照に`setDoc()`で値を設定しています。

###リスト6-13
```JavaScript:pages/address/info.js
import { useState, useEffect } from 'react';
import Layout from '../../components/layout';
import { getFirestore, collection, query, orderBy, doc, addDoc, updateDoc, getDoc, getDocs } from 'firebase/firestore/lite';
import { getAuth } from 'firebase/auth';
import { useRouter } from 'next/router';
import '../../components/fire';

const db = getFirestore();
const auth = getAuth();

export default function Info() {
  const [message, setMessage] = useState('address info');
  const [cmt, setCmt] = useState('');
  const [mydata, setMydata] = useState(null);
  const [msgdata, setMsgdata] = useState([]);
  const router = useRouter();

  useEffect(() => {
    if (auth.currentUser == null) {
      router.push('/address');
    }
  }, []);

  const onChangeCmt = (e => {
    setCmt(e.target.value);
  });

  const doAction =(_ => {
    const t = new Date().getTime();
    const to = {
      comment: 'To: ' + cmt,
      time: t
    };
    const from = {
      comment: 'From: ' + cmt,
      time: t
    };
    addDoc(collection(db, 'address', auth.currentUser.email, 'address', router.query.id, 'message'), to).then(_ => {
      addDoc(collection(db, 'address', router.query.id, 'address', auth.currentUser.email, 'message'), from).then(_ => {
        updateDoc(doc(db, 'address', router.query.id, 'address', auth.currentUser.email), {flag: true}).then(_ => {
          router.push('/address');
        });
      });
    });
  });

  const goBack = _ => {
    router.push('/address');
  };

  useEffect(() => {
    if (auth.currentUser != null) {
      getDoc(doc(db, 'address', auth.currentUser.email, 'address', router.query.id)).then(snapshot => {
        setMydata(snapshot.data());
      });
      getDocs(query(collection(db, 'address', auth.currentUser.email, 'address', router.query.id, 'message'), orderBy('time', 'desc'))).then(snapshot => {
        const data = [];
        snapshot.forEach(document => {
          data.push(
            <li className="list-group-item px-3 py-1" key={document.id}>
              {document.data().comment}
            </li>
          );
        });
        setMsgdata(data);
      });
      updateDoc(doc(db, 'address', auth.currentUser.email, 'address', router.query.id), {flag: false});
    } else {
      setMessage("no data");
    }
  }, [message]);

  return <>
    <Layout header="Next.js" title="Info & messages.">
      <div className="alert alert-primary text-center">
        <h5 className="mb-4">{message}</h5>
        <div className="text-left">
          <div>
            <div>Name: {mydata != null ? mydata.name : ''}</div>
            <div>Mail: {mydata != null ? mydata.mail : ''}</div>
            <div>Tel: {mydata != null ? mydata.tel : ''}</div>
            <div>Memo: {mydata != null ? mydata.memo : ''}</div>
          </div>
          <hr />
          <div className="form-group">
            <label>Message:</label>
            <input type="text" onChange={onChangeCmt} className="form-control" />
          </div>
        </div>
        <button onClick={doAction} className="btn btn-priary">
          Send Message
        </button>
        <button onClick={goBack} className="btn">
          Go Back
        </button>
      </div>
      <ul className="list-group">
        {msgdata}
      </ul>
    </Layout>
  </>;
}
```
コレクションへの参照やドキュメントへの参照を取り出して、そこから値を取得したり、値を追加・更新したりしています。

##参考
[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/f61f7b90dc0ad46b3565)
[《React.js&Next.js超入門 第2版》6章をFirebase JavaScript SDK v9で動かす その２](https://qiita.com/sgigagaeru/items/b5a752f75986312fd6fb "")


[^datamodel]: https://firebase.google.com/docs/firestore/data-model を参照