花了兩天稍微了解 FireStore Database 的 CRUD 後,今天著手將我使用先前做好的表單元件們,組裝成店家資料維護頁面,但一直卡在明明登入了 user ,卻一直讀不到 user 的問題上,我仔細觀察了 API,並使用 console.log
與 Redux 工具重複查看 n 次,找不太到問題,所以設了停損點,先開始做其他的...
在網頁流程規劃中,有些頁面是沒有登入就看不見的、有些頁面是某些身分才可以看見,通常可以加上判斷讓按鈕狀態設為 disabled,不去觸發連結自然可以避免。但為了防止使用者直接在網址中輸入,打開不提供觀看的頁面之狀況,我選擇這樣操作:
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in
} else {
// User is signed out
}
});
onAuthStateChanged
獲取登入者的 currentUser
屬性,找到該登入者的資料。官方文件中有提醒「當 auth 尚未完成初始化,currentUser 也可能為 null」,所以可以使用下列程式碼的寫法,在對的時機點取得 userData
。我想我一早是踏入了這個坑裡,我直接在資料維護頁 call api,所以才一直讀不到 user...
const useAuthStatus = () => {
const [loggedIn, setLoggedIn] = useState(false);
const [checkingStatus, setCheckingStatus] = useState(true);
useEffect(() => {
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
const userData = user.currentUser;
setLoggedIn(true);
}
setCheckingStatus(false);
});
}, []);
return { loggedIn, checkingStatus };
};
const PrivateRoute = () => {
const { loggedIn, checkingStatus } = useAuthStatus();
if (checkingStatus) {
return (
<div>
{/* 這裡放 loading 效果 */}
</div>
);
}
return (
loggedIn ? <Outlet /> : <Navigate to="/login" />
);
}
/profile
頁時,先進到 <PrivateRoute />
元件(上述第5點)去判斷了。<Route path="/profile" element={<PrivateRoute />}>
<Route path="/profile" element={<Profile />} />
</Route>
依照當初 Flow Chart 的設計,如果使用者註冊時選擇的是店家,他會有資料維護的頁面,用於編輯店家資料。目前要做的是下圖這頁:
關於這頁的 Wireflame 可以參考這篇文章
updateProfile
和 updateDoc
,也要注意它們兩個函式引用的地方不同。
- 這邊也有個特別的坑跟大家分享:如果只是要更新某位使用者原有的資料(Firebase Authentication 原本就有的資料欄位),可以使用
updateDoc
這個函式;但是如果你想新增一些客製化的欄位,就要使用setDoc
覆蓋掉原本的那筆資料。- 如果看到「FirebaseError: Function updateDoc() called with invalid data. Unsupported field value: a custom ProactiveRefresh object (found in field proactiveRefresh in document」這樣的錯誤訊息,就是代表你應該使用
setDoc
取代updateDoc
。- 官方文件:覆寫單一文檔
import { getAuth, updateProfile } from "firebase/auth";
import { db } from '../../firebase';
import { doc, serverTimestamp, setDoc, updateDoc } from "firebase/firestore";
auth.currentUser.displayName
(原有 Firebase Authentication 資料中的 displayName 欄位) 和 formData.name
(實際頁面的店名)不同時,就呼叫 updateProfile
的函式。溫馨提醒:以下內容看到的
showNotify
是我個人封裝使用的提示彈窗,並不是 Firebase 的功能。有需要的話,可以放入自己需要的內容或執行其他處理。
async function onSubmit(e) {
e.preventDefault();
try {
const auth = getAuth();
const docRef = doc(db, "users", auth.currentUser.uid);
// update display name in Firebase Authentication
if (auth.currentUser.displayName !== formData.name) {
await updateProfile(auth.currentUser, {
displayName: formData.name,
});
await setDoc(docRef, {
role: 1 || null,
displayName: formData.name || null,
address: formData.address || null,
industry: "beauty" || null,
introduction: formData.introduction || null,
introduction: formData.introduction || null,
timestamp: serverTimestamp() || null,
});
}
console.log("auth.currentUser.uid =>", auth.currentUser.uid);
showNotify("success", "更新成功");
} catch (error) {
console.log("error =>", error);
showNotify("error", "更新失敗");
}
};
那如果有需要更新或加入其他新欄位與對應值,就多呼叫一個 setDoc
函式,以正確的更新 FireStore Database 裡對應 id 文件的資料。
async function onSubmit(e) {
e.preventDefault();
try {
const auth = getAuth();
const docRef = doc(db, "users", auth.currentUser.uid);
if (auth.currentUser.displayName !== formData.name) {
await updateProfile(auth.currentUser, {
displayName: formData.name,
});
// update data in FireStore
await setDoc(docRef, {
role: 1 || null,
displayName: formData.name || null,
address: formData.address || null,
introduction: formData.introduction || null,
timestamp: serverTimestamp() || null,
});
}
console.log("auth.currentUser.uid =>", auth.currentUser.uid);
showNotify("success", "更新成功");
} catch (error) {
console.log("error =>", error);
showNotify("error", "更新失敗");
}
};
今天是採坑的一天,雖然和習慣的有後端同事一起開發的模式不同,沒有 postman 試戳 API 看傳送內容和 error 訊息等,但還好已經有雛型並且實作更新 API 成功了,也算是一種進步吧!明天還有一天國慶假期,持續把握時間趕進度...