Closed godmmt closed 1 day ago
src/lib/utils/api_handler.ts
```ts import { APIConfig } from '@/constants/api_connection'; import { IAPIName, IAPIConfig, IAPIInput, IAPIResponse } from '@/interfaces/api_connection'; import useAPIWorker from '@/lib/hooks/use_api_worker'; import useAPI from '@/lib/hooks/use_api'; import eventManager from '@/lib/utils/event_manager'; import { STATUS_CODE, STATUS_MESSAGE } from '@/constants/status_code'; function checkInput(apiConfig: IAPIConfig, input?: IAPIInput) { // TODO: (20240504 - Luphia) check if params match the input schema if (!input) { throw new Error('Input is required'); } return true; } function APIHandler( apiName: IAPIName, options: IAPIInput = { header: {}, body: {}, params: {}, query: {}, }, triggerImmediately: boolean = false, cancel: boolean = false ): IAPIResponse { const apiConfig = APIConfig[apiName]; if (!apiConfig) throw new Error(`API ${apiName} is not defined`); checkInput(apiConfig, options); let response = {} as IAPIResponse; if (apiConfig.useWorker) { response = useAPIWorker(apiConfig, options, triggerImmediately, cancel); } else response = useAPI(apiConfig, options, triggerImmediately, cancel); const { code } = response; if (code === STATUS_CODE[STATUS_MESSAGE.UNAUTHORIZED_ACCESS]) { eventManager.emit(STATUS_MESSAGE.UNAUTHORIZED_ACCESS); } return response; } export default APIHandler; ```
https://github.com/user-attachments/assets/64329799-8b6c-4857-8ff5-47cb52c9eb42
APIHandler 會引發 Error: Invalid hook call 的原因可能是因為
不能在 useEffect 裡面寫 const { trigger: listUserCompanyAPI } = APIHandler<ICompanyAndRole[]>(APIName.LIST_USER_COMPANY);
以及
就算在元件中先取得 trigger 函數 但把 trigger 放入 useEffect 中使用的話 無法正確放入依賴項 (遵循 Eslint 規則) 如果放入 會瘋狂觸發 useEffect
問題可能來自於在 APIHandler
中直接呼叫 useAPI
或 useAPIWorker
,但 APIHandler
本身既不是 React 元件,也不是自訂 Hook。
這違反了 Hooks 規則,因為 React Hook 僅能在以下兩種情境中被呼叫:
在 APIHandler
函數中,React 無法辨識其為有效的上下文,直接呼叫像 useAPI
這樣的 Hook 會導致「Invalid hook call」錯誤。
然後確保內部呼叫的 Hook(useAPI
和 useAPIWorker
)遵守 Hooks 規則。
APIHandler
,回傳的 response
就是一個標準的物件,不需要使用 Hook 的規範(例如 useEffect
或 useState
)。APIHandler
不需要在 React 元件內使用,這種方式更靈活。useAPI
和 useAPIWorker
,即使其中一個結果沒有被用到,會浪費資源。```ts function APIHandler( apiName: IAPIName, options: IAPIInput = { header: {}, body: {}, params: {}, query: {}, }, triggerImmediately: boolean = false, cancel: boolean = false ): IAPIResponse { const apiConfig = APIConfig[apiName]; if (!apiConfig) throw new Error(`API ${apiName} is not defined`); checkInput(apiConfig, options); // 呼叫兩個 Hook,確保它們的執行順序一致 const workerResponse = useAPIWorker(apiConfig, options, triggerImmediately, cancel); const apiResponse = useAPI(apiConfig, options, triggerImmediately, cancel); // 根據條件決定使用哪一個結果 const response = apiConfig.useWorker ? workerResponse : apiResponse; const { code } = response; if (code === STATUS_CODE[STATUS_MESSAGE.UNAUTHORIZED_ACCESS]) { eventManager.emit(STATUS_MESSAGE.UNAUTHORIZED_ACCESS); } return response; } ```
這種方式是將 APIHandler
本身改為一個 Hook,並直接遵循 React Hooks 的設計模式。
apiConfig.useWorker
條件只執行對應的 Hook,提升性能。APIHandler
的地方,需要改為遵循 Hooks 的使用方式。```ts import { APIConfig } from '@/constants/api_connection'; import { IAPIName, IAPIConfig, IAPIInput, IAPIResponse } from '@/interfaces/api_connection'; import useAPIWorker from '@/lib/hooks/use_api_worker'; import useAPI from '@/lib/hooks/use_api'; import eventManager from '@/lib/utils/event_manager'; import { STATUS_CODE, STATUS_MESSAGE } from '@/constants/status_code'; function checkInput(apiConfig: IAPIConfig, input?: IAPIInput) { // TODO: (20240504 - Luphia) check if params match the input schema if (!input) { throw new Error('Input is required'); } return true; } const useAPIHandler = ( apiName: IAPIName, options: IAPIInput = { header: {}, body: {}, params: {}, query: {}, }, triggerImmediately: boolean = false, cancel: boolean = false ): IAPIResponse => { const apiConfig = APIConfig[apiName]; if (!apiConfig) throw new Error(`API ${apiName} is not defined`); checkInput(apiConfig, options); // Choose the appropriate hook based on `useWorker` const response = apiConfig.useWorker ? useAPIWorker(apiConfig, options, triggerImmediately, cancel) : useAPI(apiConfig, options, triggerImmediately, cancel); // Handle unauthorized access event if (response.code === STATUS_CODE[STATUS_MESSAGE.UNAUTHORIZED_ACCESS]) { eventManager.emit(STATUS_MESSAGE.UNAUTHORIZED_ACCESS); } return response; }; export default useAPIHandler; ```
APIHandler
不僅僅在 React 元件中使用,還可能在其他邏輯代碼中被調用(例如工具類函式),那麼保持它作為一般函式是更好的選擇。useAPI
和 useAPIWorker
的執行影響很小),這種方式會更簡單。APIHandler
的邏輯完全依賴於 React 的狀態管理(例如 useState
、useEffect
)或其他 Hook,那麼將它改為自定義 Hook 是更自然的選擇。短期目標:
如果需要快速解決問題,且目前的設計滿足需求,保持 APIHandler
作為一般函式即可。
長期目標:
如果將來項目更偏向 React Hooks 的設計模式(例如統一使用自定義 Hook 管理邏輯),考慮將 APIHandler
改為 useAPIHandler
是更好的策略。
已經確認過我們使用 APIHandler
的地方都在元件或 context 內 (Hook) ✅
所以我們是可以採用方法 2
但程式碼的變動範圍會比較大 目前時程比較趕 所以決定先用方法 1 暫時解決這個錯誤
等之後可以回頭改善程式碼品質的時候 再一次採用方法 2 來修改
https://github.com/user-attachments/assets/813a9aab-6299-4ea1-a060-1377d26ad067
用方法 1 還是無法避免一個問題
就是 APIHandler 回傳的 trigger 函數是不能放在 useEffect 的依賴項陣列裡面 會瘋狂的觸發
也許改成自訂 Hook 後 trigger 也可放在 useEffect 的依賴項陣列裡面、 或者甚至直接在 useEffect 內執行 APIHandler 並拿到 trigger 這樣甚至不用把 trigger 放在依賴項
const { trigger: listUserCompanyAPI } = APIHandler<ICompanyAndRole[]>(APIName.LIST_USER_COMPANY); 不能寫在 useEffect 裡面
took 3 hours
done
APIHandler 會引發 Error: Invalid hook call
並且他回傳的 trigger 函數是不能放在 useEffect 的依賴項陣列裡面 會瘋狂的觸發 useEffect
檔案位置: