Reactの続きです。以前、React環境からFirebaseのRealtime DatabaseをFetch APIで読み込みました。今回はNext.js環境からRealtime Database、Firestore、ついでにスプレッドシート(Google Sheets API)のデータを読み込んでみます。完成品をデプロイしたVercel環境上でも問題なく動きました♪それではいきましょう!
【目次】
- 作ったもの
- ベースとなった環境(Nextスターターキット)
- Fetch APIでデータを読み込む(Innerコンポーネント)
- JSONファイルのデータを読み込む
- スプレッドシートのデータをFetch APIで読み込む
- Realtime DatabaseのデータをFetch APIで読み込む
- FirestoreのデータをFetch APIで読み込む
- 最後に
※参考:前回記事
【React】フックとRealtime DatabaseをFetch APIで連携(React + Firebase環境) - クモのようにコツコツと
※参考:Reactを習得するためにやったことまとめ
qiita.com
作ったもの
プレビュー
コード
※参考:GitHub - ryo-i/firebase-test
ベースとなった環境(Nextスターターキット)
以前、作成したNextスターターキットをベースにする。Next.js + TypeScript + CSS in JS環境。
プレビュー
※参考:Nextアプリスターターキット
コード
※参考:GitHub - ryo-i/next-app-started
詳細はこちらを参照
※参考:【React】ReactでWebアプリを作るシリーズまとめ(随時更新) - Qiita
Fetch APIでデータを読み込む(Innerコンポーネント)
InnerコンポーネントでFetch APIでデータを読み込み、ページに表示する
※参考:firebase-test/Inner.tsx at main · ryo-i/firebase-test · GitHub
import部分
import React, { useState, useEffect, useContext } from 'react'; import { Context } from '../pages/index';
- Reactから
useState
,useEffect
,useContext
をimport - indexページから
Context
をimport
APIのurlをindexページのContextから読み込む形にした
※参考:フック API リファレンス – React
※参考:【React】フック(React Hooks)事始め:useState、useEffect、useContext - クモのようにコツコツと
Innerコンポーネントの本体
// Component function Inner() { // 中略 }
以下、Innerコンポーネントの中身
フック設定
// Hooks const [error, setError] = useState(null); const [isLoaded, setIsLoaded] = useState(false); const [members, setMembers] = useState([]) const context = useContext(Context); const url = context.url; console.log('url', url);
エラーerror
、読み込み中isLoaded
、データmembers
、コンテキストcontext
、コンテキスト内のURLurl
Fetch APIでurl
からデータを読み込む
useEffect(() => { async function getJsonData (url) { try { const res = await fetch(url); const resJson = await res.json(); setIsLoaded(true); const getMembers = resJson; console.log('getMembers', getMembers); setMembers(getMembers); } catch(error) { setIsLoaded(true); setError(error); console.log('err', error); } }; getJsonData(url); }, []);
useEffect()
でページ読み込み時に1回実行するgetJsonData()
関数の引数はurl
でそこからAPIのデータを読み込む- try(成功時):フック
isLoaded
をtrueにし、フックmembers
にレスポンスのデータ内容をセット - catch(失敗時):フック
isLoaded
をtrueにし、フックerror
にエラー内容をセット
ReactでAPIを叩く方法
※参考:AJAX と API – React
※参考:【React】fetchメソッドでREST APIを叩く方法 - omathin blog
Fetch APIをthenからasync/awaitの書き換え
※参考:【JS】async/await構文で書いたFetch APIからJSONデータを読み込む - クモのようにコツコツと
JSX部分
// JSX if (error) { return <p>エラー: {error.message}</p>; } else if (!isLoaded) { return <p>読み込み中...</p>; } else { return ( <> <dl> <dt>ビートルズ:</dt> <dd> <ul> {members.map(member => ( <li key={member.id}> {member.name}({member.part}) </li> ))} </ul> </dd> </dl> </> ); }
- エラーだったらエラー内容を表示
- 読み込み中だったら「読み込み中」と表示
- それ以外はデータの内容を表示
最後にInner
コンポーネントをエクスポート
export default Inner;
JSONファイルのデータを読み込む
まずNextスターターキット内にJSONファイルを置いて、そのデータをFetch APIで読み込んでみる。
member.json作成
「public/data」フォルダの中にmember.jsonを作成
[ { "id":1, "name":"ジョン・レノン", "part":"ギター" }, { "id":2, "name":"ポール・マッカートニー", "part":"ベース" }, { "id":3, "name":"ジョージ・ハリスン", "part":"ギター" }, { "id":4, "name":"リンゴ・スター", "part":"ドラム" } ]
内容はビートルズの名前とパートのみ。
※参考:firebase-test/member.json at main · ryo-i/firebase-test · GitHub
indexページでJSONファイルのurlからデータを読み込む(index.tsx)
各種データの読み込みはindexページで行う
※参考:firebase-test/index.tsx at main · ryo-i/firebase-test · GitHub
先ほどのInnerコンポーネントをimportする
import Inner from '../components/Inner';
先ほどのjsonファイルのurlを設定
const url = { json: { url: 'data/member.json' }, // 中略 };
コンテキストのexport設定(初期値はurl.json
にしておく)
export const Context: React.Context<{url: string;}> = createContext(url.json);
Homeコンポーネントの中でContextからJSONのurlをInnerコンポーネントに渡す
function Home() { return ( <> // 中略 <section> <h2>JSONファイルから読み込み</h2> <Context.Provider value={url.json} > <Inner /> </Context.Provider> </section> // 中略 </> ) }
結果
InnerコンポーネントのFetch APIでmember.jsonからのレスポンスを取得して
ビートルズメンバーが表示された!
スプレッドシートのデータをFetch APIで読み込む
次にスプレッドシートのデータを読み込んでみる。
元のスプレッドシートデータ
スプレッドシートでビートルズメンバーの表を作成
Google Sheets APIでスプレッドシートを叩く
Google Sheets APIを使うとスプシのデータのAPIを叩いてJSONのレスポンスを取得できる!
※参考:Sheets API | Google Developers
※参考:【最新版】Google SpreadSheetのデータをJSONで取得する手順 | マコブログ
いろいろな取得方法があるが今回はシンプルな「values」にしてみる。
※参考:Google Sheets APIでセルの値を読み込む方法 - Qiita
※参考:Googleスプレッドシートをプログラムから操作 - Qiita
スプシのURLからIDを取得、GCPでGoogle API Keyを設定すると下記のurlを開くと
https://sheets.googleapis.com/v4/spreadsheets/(スプシのID)/values/(シート名)?key=(APIキー)
(スプシファイルの「共有」設定は「リンクを知っている全員に変更」に変更する)
JSONデータが表示された!
スプシから帰ってくるデータは二次元配列形式になっている。配列の1つ目がキー名になっている。
配列を連想配列に変換
二次元配列形式のままだとInnerコンポーネントでデータをうまく取得できない。これをキーと値の連想配列の形に変換したい。
下記のGASでスプレッドシートの配列の1行目をキー名にして連想配列化している方法がとても参考になった!
※参考:【GAS】スプレッドシートのデータからオブジェクトを作る方法|もりさんのプログラミング手帳
新たに「Inner2」コンポーネントを作成して「Inner」コンポーネントの内容を改造した。
※参考:firebase-test/Inner2.tsx at main · ryo-i/firebase-test · GitHub
urlからレスポンスのjsonを取得するとこまでは一緒
const res = await fetch(url); const resJson = await res.json(); setIsLoaded(true); const getMembers = resJson; console.log('getMembers', getMembers);
配列の1つ目からキー名の変数を設定
const keyId = getMembers.values[0][0]; const keyName = getMembers.values[0][1]; const keyPart = getMembers.values[0][2];
新たに空の配列を作り、先ほどのキー名と値の連想配列を入れる
const resultMembers = []; for (var i = 1; i < getMembers.values.length; i++) { const thisMember = {}; thisMember[keyId] = getMembers.values[i][0]; thisMember[keyName] = getMembers.values[i][1]; thisMember[keyPart] = getMembers.values[i][2]; resultMembers.push(thisMember); } console.log('resultMembers', resultMembers);
変数i
の初期値を1
にすることで2つ目から実行される
多次元配列が連想配列の形式に変換された!
変換した方の結果をフックmembers
に入れる
setMembers(resultMembers);
APIキーをVercelの環境変数で設定
GitHub上にGoogle API Key付きのurlを載せたところ「GitGuardian」からセキュリティについて警告メールが届いた。。
※参考:GitGuardian なるものからメールが来た件 – .NET ゆる〜りワーク
※参考:Google APIキーを間違えてパブリックリポジトリに公開するとこうなる | tr`s log
下記の方法にするのが安全そう
- GitHub上に公開したAPIキーをGCPで削除する
- GCPで新たなAPIキーを再発行
- 新たなAPIキーをVercelの環境変数設定
これでAPIキーをGitHubを介さずに取得できる
※参考:Vercelに Firebase Admin SDK の秘密鍵の情報を設定する
Next & Vercelでの環境変数の設定方法
※参考:Basic Features: 環境変数 | Next.js
「Settings」の「Environment Variables」でキーと値を入れて設定する
環境変数をローカル環境で取得するにはルート直下に「.env.local」ファイルを作成し、その中に環境変数を設定する。変数名の頭にはNEXT_PUBLIC_
付ける
NEXT_PUBLIC_GOOGLE_KEY=(APIキー)
※参考:【Node.js/Next.js】環境変数(.env)のチートシート|2022年1月版
Vercelのデプロイ環境では無しでも取得できるがローカルで動作確認したいので付けるものと思っておいたほうがいい。
また、環境変数の変更したあと、Vercel上に変更が反映されなかった。
下記の「View build logs」の「Promote to Production」をクリックする方法で反映された!
※参考:Vercel Serverless Functions で環境変数が読み込まれない
Google Sheets APIのurlを設定
index.tsxの変数key
に先ほどの環境変数を設定
const key = process.env.NEXT_PUBLIC_GOOGLE_KEY;
process.env.環境変数名
で取得できる
url
オブジェクトのspreadsheet
キーにurlを設定
const url = { // 中略 spreadsheet: { url: 'https://sheets.googleapis.com/v4/spreadsheets/(スプシのID)/values/beatles?key=' + key }, // 中略 };
APIキー部分は変数key
を組み合わせて生成する
index.tsxでurl.spreadsheet
のurlをInner2コンポーネントに渡す
function Home() { return ( <> // 中略 <section> <h2>スプレッドシートから読み込み</h2> <Context.Provider value={url.spreadsheet} > <Inner2 /> </Context.Provider> </section> // 中略 </> ) }
結果
Inner2コンポーネントのFetch APIでスプレッドシートからのレスポンスを取得して
ビートルズメンバーが表示された!
Realtime DatabaseのデータをFetch APIで読み込む
次にFirebaseのRealtime Databeseのデータを読み込んでみる。
JSONデータのインポート
Realtime DatabeseにはJSONをインポートする機能があるので「member.json」をインポートしてみる
「member.json」がそのままの形でインポートされた。まったくカン・タン・だ!
なお、Realtime Databaseは全体が一つのJSONデータのため、データ全体が丸見えになってしまう。キー階層ごとのルール付けで回避でるので、種類が異なるデータの時には設定したい。
※参考:Firebase Realtime Database セキュリティ ルールを理解する
※参考:FirebaseのRealtime Databaseでルールを試してみる - techium
Realtime DatabeseのAPIのurlを設定
Realtime Databeseはurlの末尾に「.json」を追加するとJSON形式のデータを取得できる♪
※参考:[:title]【React】フックとRealtime DatabaseをFetch APIで連携(React + Firebase環境) - クモのようにコツコツと
index.tsxでurl
オブジェクトのrealtimeDatabase
キーにRealtime Databeseのurlを設定
const url = { // 中略 realtimeDatabase: { url: 'https://fir-test-79045-default-rtdb.asia-southeast1.firebasedatabase.app/.json' }, // 中略 };
realtimeDatabase
のurlをInnerコンポーネントに渡す
function Home() { return ( <> // 中略 <section> <h2>Realtime Databaseから読み込み</h2> <Context.Provider value={url.realtimeDatabase} > <Inner /> </Context.Provider> </section> // 中略 </> ) }
結果
InnerコンポーネントのFetch APIでRealtime Databaseからのレスポンスを取得して
ビートルズメンバーが表示された!
FirestoreのデータをFetch APIで読み込む
最後にFirebaseのFirestoreのデータを読み込んでみる。
Firestoreとは
Realtime Databaseより後にできたNoSQLデータベース
※参考:Add Firebase to your JavaScript project
realtime databaseとfirebaseの比較
- Firestoreはデータの範囲を細かく制御できる(Realtime Databaseはデータが丸見え)
- Realtime DatabaseはJSONの一括インポート・エクスポートが強い
どちらも良し悪しがある
※参考:Firebase Realtime DatabaseとFirestoreを使い分けていこうなという話 - KAYAC engineers' blog
firestoreのリージョンは「asia-northeast1」が東京
※参考:Cloud Firestore のロケーション | Firebase
FirestoreのセキュリティルールはRealtime Databaseとは違いJSON形式ではない記述方式。ちゃんと理解する必要がありそうだが、かなりのことができそう。
※参考:Cloud Firestore セキュリティ ルールを使ってみる | Firebase
※参考:知っておいて損はない、Firebaseのセキュリティルールのこと - ログミーTech
データを入力
realtime databaseからfirestoreにデータを移行方法(一括自動変換は難しい)
※参考:Firebase Realtime Database で Cloud Firestore を使用する
今回はデータ量も少ないので、コンソール上で手入力した
データが登録された!
Firestoreのurlを設定
Firestoreのurlは下記のようになる
https://firestore.googleapis.com/v1/projects/(プロジェクトID)/databases/(default)/documents/(コレクション)/(ドキュメントID)
※参考:Cloud Firestore REST API を使用する | Firebase
例えば「/member/1」だと1のジョン・レノンのみのデータを取得できる
「/member」だとすべてのドキュメントIDの情報を取得できた。今回はこっちを使う。
Realtime Databaseに比べるとデータは結構深い階層にある。
Firebaseのデータを取得
Innerコンポーネントのままだとデータの階層が合わないのでInner3コンポーネントを作って改造する。
fetch()
でデータを取得、ここまではこれまでと同じ
const res = await fetch(url); const resJson = await res.json(); setIsLoaded(true); const getMembers = resJson.documents; console.log('getMembers', getMembers);
新たなオブジェクトを作って、Firebase内の欲しい階層の値を取得して入れる
const resultMembers = []; for (var i = 0; i < getMembers.length; i++) { const thisMember = { id: 0, name: '', part: '' }; thisMember.id = getMembers[i].fields.id.integerValue; thisMember.name = getMembers[i].fields.name.stringValue; thisMember.part = getMembers[i].fields.part.stringValue; resultMembers.push(thisMember); } console.log('resultMembers', resultMembers);
結果、これまでのJSONと同じ形式に変換できた!
変換後のデータをフックmembers
にセット
setMembers(resultMembers);
FirestoreのAPIのurlを設定
index.tsxのurl
オブジェクトのfirestore
キーにFirestoreのurlを設定
const url = { // 中略 firestore: { url: 'https://firestore.googleapis.com/v1/projects/(プロジェクトID)/databases/(default)/documents/member/' } };
firestore
のurlをInner3コンポーネントに渡す
function Home() { return ( <> // 中略 <section> <h2>Firesroteから読み込み</h2> <Context.Provider value={url.firestore} > <Inner3 /> </Context.Provider> </section> </> ) }
結果
Inner3コンポーネントのFetch APIでFirestoreからのレスポンスを取得して
ビートルズメンバーが表示された!
最後に
全体をさわってみた感想
- Google Sheets API:大量のデータを編集するにはスプシのテーブル形式は使いやすい!
しかし、レスポンスが連想配列ではないのが不便かも? - Realtime Database:ローカルのJSONファイルを一括インポートするのが楽!
しかし、データ全体が一つのJSONデータのため、ルール付けによってはデータが丸見えになってしまうのが不便かも? - Firestore:コレクションごとにデータの範囲を制御できるのが便利そう!
しかし、JSON一括インポートがなかったり階層が深いのが不便かも?
それぞれにメリットデメリットがある感じがしました。
また、今回は読み込みだけですが、追加、削除、修正などCRUD操作をする場合や、そのセキュリティルール設定も考慮するとまた話が変わっていくかもしれません。
それではまた!
※参考:Reactを習得するためにやったことまとめ
qiita.com