React Firebase 連携

構築

npx create-react-app . --template typescript
npm start

立ち上がっていればOK

必要なライブラリをインストール

npm i @material-ui/core
npm i @material-ui/icons
npm i firebase
npm i react-router-dom @types/react-router-dom
npm start

型をインストールする

yarn add -D @types/react

※reactは typescriptで書かれてない上、
型提供も元パッケージでしていないので型を別でインストールする必要がある

-Dオプション
devDependencies:開発用の依存関係

アローファンクションのテンプレートを使用できる

rafce

Firebaseとの連携

.envファイルに環境変数を設定する

REACT_APP_FIREBASE_APIKEY=""
REACT_APP_FIREBASE_DOMAIN=""
REACT_APP_FIREBASE_DATABASE=""
REACT_APP_FIREBASE_PROJECT_ID=""
REACT_APP_FIREBASE_STORAGE_BUCKET=""
REACT_APP_FIREBASE_SENDER_ID=""
REACT_APP_FIREBASE_APP_ID=""

firebase.tsを作成する

import firebase from "firebase/app";
import "firebase/app";
import "firebase/firestore";
import "firebase/auth";

const firebaseApp = firebase.initializeApp({
  apiKey: process.env.REACT_APP_FIREBASE_APIKEY,
  authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
  databaseURL: process.env.REACT_APP_FIREBASE_DATABASE,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messageingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID,
});

export const db = firebaseApp.firestore();
export const auth = firebase.auth();

Firebaseにてデータベースを作成する
 コレクションを開始
 ドキュメントIDを自動生成
 フィールド入力(titleなど)
 タイプを指定(stringなど)
 値を入力(任意)

React側で取得する

import { List } from "@material-ui/core";
import React,{useState, useEffect} from 'react';
import './App.css';
import { db } from "./firebase";

const App: React.FC = () => {
  const [tasks, setTasks] = useState([{id:"", title:""}]);
  useEffect(()=> {
    const unSub = db.collection("tasks").onSnapshot((snapshot)=>{
      setTasks(
        snapshot.docs.map((doc)=> ({id: doc.id, title: doc.data().title}))
      );
    });
    return ()=> unSub();
  },[]);

  return (
    <div className="App">
      {tasks.map((task) => (
        <h3>{task.title}</h3>
      ))}
    </div>
  );
};

export default App;

firebaseから取得したtasksの内容をReact側でstateとして保持しておきたいためuseStateを使用する

// 空の内容で初期化
const [tasks, setTasks] = useState([{id:"", title:""}]);

アプリケーションが立ち上がった時にfirebaseへアクセスして
存在するデータベースのtasksの内容を取得するためuseEffectを使用する
アプリケーションが起動した最初の1回のみデータを読みに行きたいため
第二引数は空にする

useEffect(()=> {},[]);

実際にデータベースへアクセスする内容
db.collectionで取得したいデータベース名を指定する
onSnapshotでデータベースの内容を取得する
firestoreから取得した内容を(snapshot)の引数にいれて実際の処理を行う
取得したtasksのオブジェクトの一覧をsetTasksを使ってtasksのstateに格納する
snapshotの複数あるdocumentをmapで展開する

  useEffect(()=> {
    const unSub = db.collection("tasks").onSnapshot((snapshot)=>{
      setTasks(
        snapshot.docs.map((doc)=> ({id: doc.id, title: doc.data().title}))
      );
    });
    return ()=> unSub();
  },[]);

取得した内容をブラウザへ表示する

const App: React.FC = () => {
  const [tasks, setTasks] = useState([{id:"", title:""}]);
  useEffect(()=> {
    const unSub = db.collection("tasks").onSnapshot((snapshot)=>{
      setTasks(
        snapshot.docs.map((doc)=> ({id: doc.id, title: doc.data().title}))
      );
    });
    return ()=> unSub();
  },[]);

  return (
    <div className="App">
      {tasks.map((task) => (
        <h3 key={task.id}>{task.title}</h3>
      ))}
    </div>
  );
};

タスクを作る

import { FormControl, TextField } from "@material-ui/core";
import React,{useState, useEffect} from 'react';
import './App.css';
import { db } from "./firebase";
import AddToPhotosIcon from "@material-ui/icons/AddToPhotos";

const App: React.FC = () => {
  const [tasks, setTasks] = useState([{id:"", title:""}]);
  const [input, setInput] = useState("");

  useEffect(()=> {
    const unSub = db.collection("tasks").onSnapshot((snapshot)=>{
      setTasks(
        snapshot.docs.map((doc)=> ({id: doc.id, title: doc.data().title}))
      );
    });
    return ()=> unSub();
  },[]);

  const newTask = (e: React.MouseEvent<HTMLButtonElement>)=>{
    db.collection("tasks").add({title: input});
    setInput("");
  }

  return (
    <div className="App">
      <h1>Todo App by React/Firebase</h1>
      <FormControl>
        <TextField
        InputLabelProps={{
          shrink: true,
        }}
        label="New task ?"
        value={input}
        onChange={(e: React.ChangeEvent<HTMLInputElement>) => setInput(e.target.value)}
        />
      </FormControl>
      <button disabled={!input} onClick={newTask}>
        <AddToPhotosIcon />
      </button>
      {tasks.map((task) => (
        <h3 key={task.id}>{task.title}</h3>
      ))}
    </div>
  );
};

export default App;

ユーザーがタイピングした文字列を保持するためのstateをuseStateを使用して実行する

// 初期値を空にセット
const [input, setInput] = useState("");

material-uiを使用してフォームを作成する

import { FormControl, TextField } from "@material-ui/core";

<FormControl>
    <TextField
      InputLabelProps={{
        shrink: true,
      }}
      label="New task ?"
      value={input}
      // ユーザーがタイピングするために呼び出される関数を定義
      // input stateの内容を上書きするためsetInputを毎回呼び出す(ユーザーがタイピングしたvalueの値を取得している)
      // typescriptではイベントオブジェクトに型を指定する必要がある
      onChange={(e: React.ChangeEvent<HTMLInputElement>) => setInput(e.target.value)}
    />
</FormControl>

送信用のボタンを作成する

import AddToPhotosIcon from "@material-ui/icons/AddToPhotos";

      // 入力フォームが空の時はボタンを押せない
      // ボタンが押された時に呼び出される関数(newTask)を指定する
      <button disabled={!input} onClick={newTask}>
        <AddToPhotosIcon />
      </button>

ボタンを押した時の呼び出される関数を作成

  const newTask = (e: React.MouseEvent<HTMLButtonElement>)=>{
    // firebaseのデータベースにタスクを追加するため、db.collectionで追加したいコレクションを指定する(addでオブジェクトを渡す)
    db.collection("tasks").add({title: input});
    // 次の新しいタスクの作成に備えてinput stateを初期化しておく
    setInput("");
  }

※onClickなどのデータ型を知りたい場合は、onClickにカーソルを合わせると表示される