iT邦幫忙

2022 iThome 鐵人賽

DAY 18
0
Modern Web

React Hook 不求人,建立自己的 Hook Libary系列 第 18

# [DAY 18] 自己的Hook自己做!AuthProvider 不是 Hook 吧?(下)

  • 分享至 

  • xImage
  •  

承接上篇,已經把登入的畫面導向製作完成了,現在要進入核心的部分,也就是驗證啦!

DEMO 在這裡~~~

開始!

假資料

先來準備一組對應的假資料,實際上不會那麼的...簡陋,token 基本是落落長且會更新,這邊先拿來應付範例使用:

const list = {
  //username to token
  Charlie: "0002",
  Danny: "0005",
  Hugh: "0008",
  YT: "0015",
 //token to username
  "0002": "Charlie",
  "0005": "Danny",
  "0008": "Hugh",
  "0015": "YT",
}

加入驗證

驗證有兩部分:

  1. 透過登入頁面,打API並將回傳資料存入 AuthProvider
  2. AuthProvider 本身會抓取 localStorage 的內容並搭配 useEffect 來決定後續動作

登入獲取Token並存起來

這一點其實 AuthProvider 已經建立好了,handleLogin 可以傳入一個參數,API回傳的資料可以藉由這邊傳入並儲存:

AuthProvider

const handleLogin = (data) => {
  dispatch({ type: "LOGIN", payload: data })
  onLogin?.(data) 
}

因此登入頁面只要加上驗證的地方,也就是呼叫登入用的API login

LoginPage

import { useAuth } from 'react'

function LoginPage () {
  const {state, handleLogin} = useAuth() 
  const [username, setUsername] = useState("")

  const handleClick = async () => {
    //這可以加入一些loading狀態切換
    
    try {
      const resData = await login(username) //會回傳 { token }
      handleLogin(resData)
      //由於導向已經在父層與Provider處理好了,這邊就不用再寫一次
    } catch (error) {
      console.log(error)
      //這可以再加入額外的錯誤處裡
    }
  }
  
  return <>
    <Input onChange={e => setUsername(e.target.value)}/>
    <Button onClick={handleClick}>LOGIN</Button>
  </>
}

另外,要把 token 存在 localStorage,因此我們稍加增加一下 onLogin 的任務,也修改了一下原本的命名避免混肴:

Dashboard 


function DashBoard (props) {
  
  const handleAfterLogin = (data) => {
    localStorage.setItem('token', data.token) 
    //to somewhere
  }
  
  const handleToLoginPage = () => {
    //to login page
  }
  
  return <AuthProvider onLogin={handleLogin} onLogout={handleLogout}>
    <Outlet/>         
  </AuthProvider>
}

handleAfterLogin 的 data 怎麼來:

/* 登入頁面的動作,呼叫了 AuthProvider 提供的 handleLogin */
const resData = await login(username)
handleLogin(resData)

/* AuthProvider 的 handleLogin 接受的資料會存起來,我們進一步將這個資料也傳入 onLogin */
const handleLogin = (data) => {
  dispatch({ type: "LOGIN", payload: data })
  onLogin?.(data) 
}

/*
** handleAfterLogin(onLogin) 主要是負責登入的後續動作,
** 除了導向,透過傳入的 data 可以讓localStorage 保存 token 
*/
const handleAfterLogin = (data) => {
  localStorage.setItem('token', data.token) 
  //to somewhere
}

這樣的拆分與傳遞主要讓 AuthProvider 的任務專注在保存以及協助呼叫登入以及登出的動作,而開發者可以更彈性的來使用(不過你可能會更偏好把所有邏輯都囊括在Provider裡面,這邊單純以共用的角度來作拆分)

抓取登入者資料以及再驗證

有了 token 之後,通常會有另一隻API GET /user/me 來知道當下登入者的資訊,以此範例我們有一個 getMe 來當作獲取資料的方式,我們可以把這個動作交給 AuthProvider

AuthProvider

function AuthProvider({
  children, 
  onLogin,
  onLogout,
  validation, //可以傳入要驗證的動作,以此範例來說是 getMe
  token, //會傳入給validation,以此範例會從 localStorage 取得
  deps = [],
}) {
//前略
  
const [validating, setValidating] = useState(false) //提供給UI顯示狀態用
  
useEffect(() => {
    const validate = async () => {
      setValidating(true)
      try {
        const data = await validation?.(token)
        dispatch({ type: "LOGIN", payload: { ...data, token } })
      } catch (error) {
        console.log(error.message)
        handleLogout()
      }
      setValidating(false)
    }

    validate()
  }, [state.isAuth, ...deps])  //使用 isAuth 來辨認需不需要再驗證

//後略
}

這樣處理了兩種情況:

  1. 初次登入:初次登入後獲取 token,token 分別存到 Provider 與 localStorage,Provider 因為 isAuth 的改變再次執行並得到 token 的使用者資訊。
  2. 再次造訪(登入之後刷新頁面):Provider 會執行 useEffect 來執行我們提供的驗證 function 以及相關資訊,來進一步決定使用者能繼續訪問或是需要重新登入。
  3. 重複驗證:依照情境如果你需要頻繁的驗證使用者,可以透過 deps 來決定何時要 trigger。

登入囉!

DEMO 在這裡~~~

這樣一來簡單的登入流程就完成啦!

可以看到登入後會有 Skeleton 閃來閃去,是藉由在 AuthProvider 提供 isPending 來進一步處理 (optional)

目前再次造訪的情境模擬會多打一次API(看起來卡卡的地方),先暫時忽略,屆時可以加入更多判斷來解決

Hook 的部分

看到現在,AuthProvider 不是 Hook 沒錯 (廢話 ( ゚Д゚)<!!

前一篇有講到說拿 AuthProvider 提供的 method 來進行登入登出,是使用 useAuth,而後續的頁面包含登出,使用者資訊等等也一樣可以透過 useAuth 取得,不過你也可以進一步拆分:

Provider

function Provider ({...}) {
//前略

const value = useMemo(
  () => ({ state, actions: { handleLogin, handleLogout } }),
  [state]
)

return (
  <AuthContext.Provider value={value}>
    {children}
  </AuthContext.Provider>
)

export const useMe = () => useContext(AuthContext).state
export const useAuth = () => useContext(AuthContext).actions
  • useMe 就是單純提供當前使用者資訊
  • useAuth 就是單純提供登入登出的方法

這一部分就是完全可以依照自己的情境來訂製需要的 hook。

結語

登入好難。


上一篇
[DAY 17] 自己的Hook自己做!AuthProvider 不是 Hook 吧?(上)
下一篇
[DAY 19] 自己的Hook自己做!useTabs 想開幾個頁籤都可以?! (上)
系列文
React Hook 不求人,建立自己的 Hook Libary30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言