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

React/Next.js中从事件处理器调用Hook的正确方式

问题分析与解决方案

核心错误原因

你遇到的Hook错误,根源在于**GetDistrictsAPI是普通函数,但里面直接调用了useSWR Hook**。React Hook的规则明确要求:

  • 只能在React组件的顶层调用
  • 只能在以use开头的自定义Hook中调用
    普通函数里调用Hook会直接触发React的Hook校验错误。

修正方案(推荐使用SWR自定义Hook)

1. 重构API服务为自定义Hook

将GetDistrictsAPI改成符合规则的自定义Hook(命名必须以use开头),利用SWR的缓存、自动请求等能力:

// @/_utils/services/useDistricts.js
import useSWR from "swr";
import axios from "axios";

// 自定义Hook必须以use开头,遵循Hook规则
export default function useDistricts(provID) {
  const fetcher = (url) => axios.get(url).then(res => res.data);
  
  // 当provID为空时,传递null作为SWR的key,避免发起无效请求
  const { data, error, isLoading } = useSWR(
    provID ? `/api/districts?province_id=${provID}` : null,
    fetcher
  );

  return {
    districts: data,
    isLoading,
    hasError: !!error
  };
}

2. 调整DisplayDistricts组件逻辑

移除冗余的handleDistricts和useCallback,直接使用自定义Hook处理数据请求,并优化交互逻辑:

'use client'

import { Field, useFormikContext } from 'formik';
import useDistricts from '@/_utils/services/useDistricts';
import { useState, useEffect } from 'react';

export default function DisplayDistricts(props) {
  const { setFieldValue, values } = useFormikContext();
  const passedProvID = props.idProvince;

  // 使用自定义Hook获取地区数据
  const { districts, isLoading } = useDistricts(passedProvID);

  // 初始化默认选项
  const [selectedDistricts, setSelectedDistricts] = useState({
    data: [{ dist_id: '0', name: 'Select a Province first' }]
  });

  // 监听地区数据变化,更新本地状态
  useEffect(() => {
    if (districts) {
      setSelectedDistricts(districts);
    } else if (!passedProvID) {
      // 未选择省份时,重置为默认提示
      setSelectedDistricts({ data: [{ dist_id: '0', name: 'Select a Province first' }] });
    }
  }, [districts, passedProvID]);

  return (
    <Field
      as="select"
      id="pxDistrict"
      name="pxDistrict"
      disabled={!values.pxProvince || isLoading}
      onChange={(e) => {
        setFieldValue('pxDistrict', e.target.value);
        // 移除多余的handleDistricts调用,省份变化时自定义Hook会自动触发请求
      }}
      className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
    >
      {/* 加载状态提示 */}
      {isLoading ? (
        <option value="">Loading districts...</option>
      ) : (
        selectedDistricts.data.map(p => (
          <option key={p.dist_id} value={p.dist_id}>{p.name}</option>
        ))
      )}
    </Field>
  )
}

替代方案(纯axios请求,无SWR)

如果不需要SWR的缓存能力,也可以把API改成普通异步函数:

// @/_utils/services/GetDistrictsAPI.js
import axios from "axios";

export default async function GetDistrictsAPI(provID) {
  if (!provID) {
    return { data: [{ dist_id: '0', name: 'Select a Province first' }] };
  }
  const res = await axios.get(`/api/districts?province_id=${provID}`);
  return res.data;
}

然后调整DisplayDistricts的useEffect:

useEffect(() => {
  const fetchDistricts = async () => {
    const data = await GetDistrictsAPI(passedProvID);
    setSelectedDistricts(data);
  };
  fetchDistricts();
}, [passedProvID]);

内容的提问来源于stack exchange,提问作者TimLavelle

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 19:27:39