React Native-串接 Firebase

終於來填這個坑。

解到死的 bug(React Native)

放置頂是因為當初一直找不到關鍵字來解這個 bug,所以花了一段時間才解出來。

總之呢,這個 bug 是只要把 firebase 引入來用時,就會出現編譯失敗然後顯示下面的錯誤訊息:

While trying to resolve module ‘idb’ from file … the package was successfully found. However, this package itself specifies a main module field that could not be resolved.
Indeed, none of these files exist:

簡單來說這是因為 firebase 使用 .cjs 的副檔名,而 Expo 跟 React Native 不支援,所以你要手動調整一些設定來讓他們支援。

在根目錄建立 metro.config.js,填入底下內容:

1
2
3
4
5
6
7
8
const { getDefaultConfig } = require("@expo/metro-config");

const defaultConfig = getDefaultConfig(__dirname);

// push .cjs 這個副檔名
defaultConfig.resolver.assetExts.push("cjs");

module.exports = defaultConfig;

如果是 React Native cli 的話:

1
2
3
4
5
6
7
8
9
const { getDefaultConfig } = require("metro-config");
const { resolver: defaultResolver } = getDefaultConfig.getDefaultValues();
exports.resolver = {
...defaultResolver,
sourceExts: [
...defaultResolver.sourceExts,
"cjs",
],
};

這樣就解決了,詳細可以參考 官方文件 或這篇 討論

基礎概念

Firebase 中有兩個主要的東西:

  1. collection 可以想成是 table 的感覺
  2. document 可以想成是欄位的意思

Firebase 是透過「路徑」來表示資料庫,比如說我有這樣的東西:

path

把資料轉換成網址的話就會像上面這張圖一樣。

懶人包 API

general

  • initializeApp 初始化,initializeApp(firebaseConfig)
  • getFirestore 建立 DB 的 reference,getFirestore(app)
  • doc 建立 document 的 reference
  • getDoc 取得單一個 document(注意沒有 s)
  • getDocs 一次取得 collection 中的所有 document
  • setDoc 建立或更新 document
  • deleteDoc 刪除 document
  • collection 用來設定 reference 的方法,例如:collection(db, 'orders')
  • serverTimestamp 取得時間戳(可用來設定 createdAt)

query 相關

  • query 拿來下指令
  • orderBy 排序
  • limit 限制數量

前置作業

首先:expo install firebase

附註:建議裝的套件 npm install tslib(能避免噴 Error)

  1. 先建立專案
  2. 跳轉後選擇「web」來建立 App
  3. 把 config 的內容記下來,等一下會用到

建立 database

  1. 從側邊欄點選「firestore database」
  2. 點選「建立資料庫」
  3. 點選「已測試模式啟動」
  4. 點選「啟用」

完成後就可以建立 collection(table) 了。

如果想要設定一些限制,可以到「規則」這個 tab 來設定。

跟資料庫連線(初始化)

詳細可以參考這個官方文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";

const firebaseConfig = {
apiKey: "...",
authDomain: "...",
projectId: "...",
storageBucket: "...",
messagingSenderId: "...",
appId: "..."
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Db reference(後續的操作都會透過他來做)
export const db = getFirestore(app);

基本的 CRUD

Create(setDoc)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { db } from "../firebase";
import { doc, setDoc } from "firebase/firestore";

export default function Com1 () {

const create = () => {
// document Reference
const docRef = doc(db, 'MyCollection', 'MyDocument');
// 要輸入的資料
const docData = {
name: 'PeaNu',
age: 30,
job: 'Front-End'
}
// Send request to firebase
setDoc(docRef, docData)
.then(() => alert('Create Succuess!'))
.catch(err => alert(err.message))
}


return <button onClick={create}>Create Document</button>
}

Read(getDoc)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { db } from "../firebase";
import { doc, setDoc, getDoc } from "firebase/firestore";
import { useEffect, useState } from "react";

export default function Com1 () {

// Store user data
const [user, setUser] = useState(null);

const read = () => {
// document Reference
const docRef = doc(db, 'MyCollection', 'MyDocument');
getDoc(docRef)
.then(snapshot => {
// Check if data exists or not
if (snapshot.exists()) {
setUser(snapshot.data());
}
else {
alert('No doc found');
}
})
.catch(err => alert(err.message))
}


return (
<>
{user && (
<div>
<h2>Name: {user.name}</h2>
<p>Age: {user.age}</p>
<p>Job: {user.job}</p>
</div>
)}
<button onClick={read}>Read Document</button>
</>
)
}

如果 collection 或 document 不存在的話就會幫你建立一個新的。反之,如果存在的話就會幫你把內容更新。

Update(setDoc)

一樣是用 setDoc 這個方法,不過會稍微做點變化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import { db } from "../firebase";
import { doc, setDoc, getDoc } from "firebase/firestore";
import { useState } from "react";

export default function Com1 () {

// Store user data
const [user, setUser] = useState(null);
// Store input value
const [value, setValue] = useState("");

const read = () => {
const docRef = doc(db, 'MyCollection', 'MyDocument');
getDoc(docRef)
.then(snapshot => {
if (snapshot.exists()) {
setUser(snapshot.data());
}
else {
alert('No doc found');
}
})
.catch(err => alert(err.message))
}


const update = (data, merge) => {
const docRef = doc(db, 'MyCollection', 'MyDocument');

// merge: true = 只更新部分資料
// merge: false = 整筆覆寫
setDoc(docRef, data, { merge: merge })
.then(() => {
alert('Update Success')
// reload screen when finish update
read()
})
.catch(err => alert(err.message))
}

return (
<>
{user && (
<div>
<h2>Name: {user.name}</h2>
<p>Age: {user.age}</p>
<p>Job: {user.job}</p>
</div>
)}
<button onClick={() => update({name: value}, true)}>Update Document</button>
<input
style={{ display: 'block', margin: '20px 0'}}
type="text"
placeholder="Update name"
onChange={e => setValue(e.target.value)}
value={value} />
</>
)
}

Delete(deleteDoc)

刪除總是最單純:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { db } from "../firebase";
import { doc, deleteDoc } from "firebase/firestore";

export default function Com1 () {


const Delete = () => {
const docRef = doc(db, 'MyCollection', 'MyDocument');
deleteDoc(docRef)
.then(() => {
alert('Delete Success');
})
.catch(err => alert(err.message))
}

return <button onClick={Delete}>Delete Document</button>
)
}

複雜一點的操作(下 Query)

假設我的需求是這樣:

  1. 抓出 collection 中的所有 document
  2. 根據 document 資料裡的時間戳排序,取出最新的一筆資料

這就會用到 query,一個可以讓我們下指令的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { db } from "../firebase";
import { getDocs, collection, query, orderBy, limit } from "firebase/firestore";
import { useEffect } from "react";

export default function Com1 () {

useEffect(() => {
const q = query(collection(db, 'orders'), orderBy('createdAt', 'desc'), limit(1));
getDocs(q).then(querySnapshot => {
querySnapshot.forEach(document => {
console.log('Document', document.data());
})
})
}, [])


return (...)
}

這邊要特別注意,如果你試著用 console.log(querySnapshot) 是拿不到真正的資料的,因為在 firebase 中「取值」這件事都得透過 getter,所以才會用 forEach 搭配 .data() 來取出 document 的值。

參考資料

React Fragment-以前沒發現到的小技巧 React Native-關於 Goole places autocomplete 的樣式
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×