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
相关产品推荐
相关产品推荐

