Next.js自定义useApiRequest Hook触发无效调用错误的问题咨询
问题原因分析
1. 初始错误的核心原因
你在点击事件处理函数handleLogin内部调用useApiRequest,违反了React Hook的强制调用规则:所有Hook必须在函数组件的顶层作用域中调用,不能嵌套在事件处理、条件判断、循环等逻辑内部。React依赖Hook的调用顺序来维护状态关联,嵌套调用会破坏这个顺序,直接触发Invalid hook call错误。
2. 改成普通函数后仍报错的原因
原useApiRequest内部使用了useState这个React Hook,而普通函数不属于组件或自定义Hook范畴,不允许调用任何React Hook,因此依然会触发相同类型的错误。
解决方案
针对你的场景(点击事件触发请求),提供两种可行的重构方案:
方案一:将请求逻辑改为纯异步函数
把请求逻辑抽离成不依赖Hook的纯异步函数,在组件内用useState管理请求结果状态。
1. 定义纯异步请求函数
// @/utils/apiRequest.ts import { ErrorResponse, SuccessResponse } from "@/types/api"; import axios, { AxiosResponse } from "axios"; export const apiRequest = async <T>( httpMethod: string, url: string, data?: any ): Promise<[boolean, T | string, string]> => { try { const apiResponse: AxiosResponse<SuccessResponse<T> | ErrorResponse> = await axios({ method: httpMethod, url, data }); if ("result" in apiResponse.data && "message" in apiResponse.data) { const successData = apiResponse.data as SuccessResponse<T>; return [true, successData.result, successData.message]; } else { const errorData = apiResponse.data as ErrorResponse; return [false, errorData.error, errorData.error]; } } catch (error) { if (error instanceof Error) { return [false, "", error.message]; } return [false, "", "Terjadi kesalahan"]; } };
2. 在组件中使用
// JadwalPage.tsx import Button from "@/components/elements/buttons/Button"; import { apiRequest } from "@/utils/apiRequest"; import Head from "next/head"; import { useState } from "react"; const JadwalPage = () => { const [isSuccess, setIsSuccess] = useState<boolean>(false); const [responseData, setResponseData] = useState<any>(null); const [message, setMessage] = useState<string>(""); const handleLogin = async () => { const [success, data, msg] = await apiRequest( "post", "/auth/login", { username: "admin", password: "pratama-putra" } ); setIsSuccess(success); setResponseData(data); setMessage(msg); success ? console.log("Data berhasil diperoleh:", data) : console.error("Gagal melakukan permintaan:", msg); }; return ( <div className="min-h-screen"> <Head> <title>Jadwal Karyawan</title> </Head> <Button onClick={handleLogin}>Click me</Button> {message && <p>{message}</p>} </div> ); }; export default JadwalPage;
方案二:重构自定义Hook,返回触发请求的函数
调整Hook设计,让它在组件顶层调用,返回一个可在事件中触发的请求函数,同时内置状态管理。
1. 重构自定义Hook
// @/hooks/useApiRequest.ts import { ErrorResponse, SuccessResponse } from "@/types/api"; import axios, { AxiosResponse } from "axios"; import { useState, useCallback } from "react"; const useApiRequest = <T>() => { const [isLoading, setIsLoading] = useState<boolean>(false); const [isSuccess, setIsSuccess] = useState<boolean>(false); const [responseData, setResponseData] = useState<T | string>(""); const [message, setMessage] = useState<string>(""); const fetchData = useCallback(async ( httpMethod: string, url: string, data?: any ) => { setIsLoading(true); try { const apiResponse: AxiosResponse<SuccessResponse<T> | ErrorResponse> = await axios({ method: httpMethod, url, data }); if ("result" in apiResponse.data && "message" in apiResponse.data) { const successData = apiResponse.data as SuccessResponse<T>; setIsSuccess(true); setResponseData(successData.result); setMessage(successData.message); } else { const errorData = apiResponse.data as ErrorResponse; setIsSuccess(false); setResponseData(errorData.error); setMessage(errorData.error); } } catch (error) { setIsSuccess(false); setResponseData(""); setMessage(error instanceof Error ? error.message : "Terjadi kesalahan"); } finally { setIsLoading(false); } }, []); return { isLoading, isSuccess, responseData, message, fetchData }; }; export default useApiRequest;
2. 在组件中使用
// JadwalPage.tsx import Button from "@/components/elements/buttons/Button"; import useApiRequest from "@/hooks/useApiRequest"; import Head from "next/head"; const JadwalPage = () => { // 在组件顶层调用Hook const { isLoading, isSuccess, responseData, message, fetchData } = useApiRequest(); const handleLogin = async () => { await fetchData( "post", "/auth/login", { username: "admin", password: "pratama-putra" } ); isSuccess ? console.log("Data berhasil diperoleh:", responseData) : console.error("Gagal melakukan permintaan:", message); }; return ( <div className="min-h-screen"> <Head> <title>Jadwal Karyawan</title> </Head> <Button onClick={handleLogin} disabled={isLoading}> {isLoading ? "Loading..." : "Click me"} </Button> {message && <p>{message}</p>} </div> ); }; export default JadwalPage;
内容的提问来源于stack exchange,提问作者Jabawi
相关产品推荐
相关产品推荐

