iT邦幫忙

2023 iThome 鐵人賽

DAY 24
1

花了兩天稍微了解 FireStore Database 的 CRUD 後,今天著手將我使用先前做好的表單元件們,組裝成店家資料維護頁面,但一直卡在明明登入了 user ,卻一直讀不到 user 的問題上,我仔細觀察了 API,並使用 console.log 與 Redux 工具重複查看 n 次,找不太到問題,所以設了停損點,先開始做其他的...

有些頁面需要被保護

在網頁流程規劃中,有些頁面是沒有登入就看不見的、有些頁面是某些身分才可以看見,通常可以加上判斷讓按鈕狀態設為 disabled,不去觸發連結自然可以避免。但為了防止使用者直接在網址中輸入,打開不提供觀看的頁面之狀況,我選擇這樣操作:

  1. 新增一個「獲取現在已登入使用者」的 hook
  2. 引入 Firebase 的身分認證與獲取現在已登入使用者的函式
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
  1. 在此 hook 中呼叫函式,確認使用者的登入狀態
onAuthStateChanged(auth, (user) => {
  if (user) {
    // User is signed in
  } else {
    // User is signed out
  }
});
  1. 使用者若為已登入,則可使用 Firebase Authentication 提供的實例
    在用戶已登入的狀態下,可以藉由呼叫 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 };
};
  1. 把此驗證之 hook 做成元件
    因為這個驗證方法主要是判斷使用者是否登入,所以我做了一個私有路由的元件,判斷如果正在確認使用者登入狀態,會看到 loading 效果,判斷完成後,登入者可以進到對應頁面,但若沒有登入,則我將其導至登入頁。
const PrivateRoute = () => {
  const { loggedIn, checkingStatus } = useAuthStatus();
  if (checkingStatus) {
    return (
      <div>
         {/* 這裡放 loading 效果 */}
      </div>
    );
  }
  return (
    loggedIn ? <Outlet /> : <Navigate to="/login" />
  );
}
  1. 在路由頁面,加入私有路由元件保護需要的頁面
    這樣做基本上就可以讓未登入的使用者使用網址進到 /profile 頁時,先進到 <PrivateRoute /> 元件(上述第5點)去判斷了。
<Route path="/profile" element={<PrivateRoute />}>
    <Route path="/profile" element={<Profile />} />
</Route>

資料維護與更新

依照當初 Flow Chart 的設計,如果使用者註冊時選擇的是店家,他會有資料維護的頁面,用於編輯店家資料。目前要做的是下圖這頁:

關於這頁的 Wireflame 可以參考這篇文章

https://ithelp.ithome.com.tw/upload/images/20230923/20140920SIk6ViMhi3.png

  1. 組裝頁面元件
    依照當初 Wireflame 所安排的去排放,今天也順手新增了 input 元件的 requird, readonly 屬性等變化。

https://ithelp.ithome.com.tw/upload/images/20231009/20140920ThTlju9TAY.png

  1. 引用 Firebase 的更新數據所需函式
    比較值得一提的是更新某位使用者,需同步呼叫 updateProfileupdateDoc,也要注意它們兩個函式引用的地方不同。
  • 這邊也有個特別的坑跟大家分享:如果只是要更新某位使用者原有的資料(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";
  1. 新增要更新資料時的 function,呼叫正確函式
    以更新使用者資料來舉例,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", "更新失敗");
    }
  };
  1. 正確完成 API 後,可以回 Firebase Console 控制台確認
    https://ithelp.ithome.com.tw/upload/images/20231009/20140920cQY1lQhFdH.png

小結

今天是採坑的一天,雖然和習慣的有後端同事一起開發的模式不同,沒有 postman 試戳 API 看傳送內容和 error 訊息等,但還好已經有雛型並且實作更新 API 成功了,也算是一種進步吧!明天還有一天國慶假期,持續把握時間趕進度...


上一篇
Day23:操作 Firestore Database 的資料(二)
下一篇
Day25:實作 Firebase Authentication 電話驗證
系列文
用 React 和 Firebase 打造一個完整 side project 吧!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言