You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 03:37:50