前回は、Firebase Authentication の機能を使って、パスワード再設定機能を実装しました。

今回は、Firestore Database からデータを取得し、ブラウザに表示させます。

まずは、Firebase の Firestore Database にアクセスし、データを作成します。

今回は、チャットを作る予定です。

コレクション名を『messages』とします。

フィールドは、text、createdAt、user の name、uid を設定しました。

image2

画面上部の『ルール』をクリックします。

image3

認証されている場合のみデータを取得したいので、if request.auth != null を設定します。

image4

『公開』をクリックしましょう。

次は、作成している React プロジェクトの Home.tsx へ移動します。

AppBarをコンポーネント化しましょう。

src フォルダに components フォルダを作成します。

components フォルダの中に Header.tsx を作成しましょう。

Home.tsx の中身をコピーし、Header.tsx 用に修正します。

import React, { useState } from "react"
import { useNavigate } from "react-router-dom"
import {
  AppBar,
  Box,
  Toolbar,
  IconButton,
  Menu,
  MenuItem,
  Snackbar,
  Alert,
} from "@mui/material"
import AccountCircle from "@mui/icons-material/AccountCircle"
import { useLogout } from "../hooks/useAuth"
import { firebaseApp } from "../firebase/firebaseConfig"

export default function Header() {
  const { logout } = useLogout()
  const navigate = useNavigate()

  const [auth, setAuth] = useState(false)

  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
  const [open, setOpen] = useState(false)

  firebaseApp.fireauth.onAuthStateChanged(user => {
    if (!user) {
      navigate("/login")
    } else {
      setAuth(true)
    }
  })

  const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
    setAnchorEl(event.currentTarget)
  }

  const handleLogout = () => {
    logout()
    setOpen(true)
    setAnchorEl(null)
    setTimeout(() => {
      navigate("/login")
    }, 2000)
  }

  const handleClose = () => {
    setAnchorEl(null)
    setOpen(false)
  }

  return (
    <Box sx={{ flexGrow: 1 }}>
      <AppBar position="static">
        <Toolbar sx={{ justifyContent: "right" }}>
          {auth && (
            <div>
              <IconButton
                size="large"
                aria-label="account of current user"
                aria-controls="menu-appbar"
                aria-haspopup="true"
                onClick={handleMenu}
                color="inherit"
              >
                <AccountCircle />
              </IconButton>
              <Menu
                id="menu-appbar"
                anchorEl={anchorEl}
                anchorOrigin={{
                  vertical: "top",
                  horizontal: "right",
                }}
                keepMounted
                transformOrigin={{
                  vertical: "top",
                  horizontal: "right",
                }}
                open={Boolean(anchorEl)}
                onClose={handleClose}
              >
                <MenuItem onClick={handleClose}>プロフィール</MenuItem>
                <MenuItem onClick={handleLogout}>ログアウト</MenuItem>
              </Menu>
            </div>
          )}
        </Toolbar>
      </AppBar>
      <Snackbar open={open} autoHideDuration={3000} onClose={handleClose}>
        <Alert onClose={handleClose} severity="success" sx={{ width: "100%" }}>
          ログアウトしました
        </Alert>
      </Snackbar>
    </Box>
  )
}

作成した Header.tsx を Home.tsx にインポートします。

<Box sx={{ flexGrow: 1 }}>
  <Header />
</Box>

データを取得するフックを作成するため、hooks フォルダに useFirebase.ts を作成します。

内容な、こちらをご覧ください。

import { useState, useEffect } from "react"
import { collection, onSnapshot } from "firebase/firestore"

import { firebaseApp } from "../firebase/firebaseConfig"

const useFirebase = (data: string) => {
  const [documents, setDocuments] = useState([])

  useEffect(() => {
    const firestore = firebaseApp.firestore
    const docRef = collection(firestore, data)
    const unsub = onSnapshot(docRef, snapshot => {
      let results: any = []
      snapshot.docs.forEach(doc => {
        results.push({ ...doc.data(), id: doc.id })
      })

      setDocuments(results)
    })
    return () => unsub()
  }, [data])

  return { documents }
}

export default useFirebase

Home.tsx にuseFirebaseをインポートします。

useFirebase には、先程 FIrestore Database で作成した『messages』コレクションを指定します。

const { documents: messages } = useFirebase("messages")

Message の型を作成しておきましょう。

createdAtは、firebase/firestoreからTimestampをインポートしておきます。

interface Message {
  id: string
  text: string
  createdAt: Timestamp
  user: {
    name: string
    uid: string
  }
}

messagesmapを使って一覧をブラウザに表示させます。

また、データがない場合には、『メッセージが存在しません』と表示させます。

<Box sx={{ flexGrow: 1 }}>
  <Header />

  {messages ? (
    messages.map((message: Message) => (
      <div key={message.id}>
        <p>{message.text}</p>
        <p>{message.createdAt}</p>
      </div>
    ))
  ) : (
    <p>メッセージが存在しません</p>
  )}
</Box>

createdAtが Firebase の timestamp なので、このままではエラーが発生します。

こちらを表示させるために、date-fns をインストールします。

ターミナルで、npm install --save date-fnsを実行します。

date-fnsからformatをインポートします。

import { format } from "date-fns"

message.createdAtを TypeScript でも読み取れるよう、message.createdAt.toDate()を追加します。

まずは、format の第一引数に、message.createdAt.toDate()、第二引数にどのように表示したいかを指定します。

今回は、2022 年 03 月 02 日と表示するようにします。

{
  format(message.createdAt.toDate(), "yyyy年MM月dd日")
}

一通り完成したので、ブラウザで確認すると、

image5

text で指定した内容と、createdAt の内容が表示されました。

次回は、Firestore Database を使い、React でメッセージ送信機能を実装します。

ブログ一覧