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

React函数组件抽离fetch函数报无效Hook调用错误如何解决?

错误产生原因
  • 违反React Hook调用规则:你在抽离的FetchCityData函数中使用了useState Hook,但你是在按钮的onClick事件回调中调用这个函数的,不符合Hook只能在React函数组件顶层作用域、或自定义Hook顶层作用域调用的规则,因此触发非法Hook调用报错。
  • 存在逻辑漏洞:FetchCityData中直接使用了未作为参数传入的city变量,且内部声明的状态和组件传入的payload/setPayload完全割裂,状态无法互通,即使解决Hook报错问题逻辑也无法正常运行。
正确的抽离方案(满足单元测试需求)

方案1:抽离纯请求逻辑(推荐,测试成本最低)

把和React状态无关的纯请求逻辑单独抽离,状态保留在组件内。抽离后的请求函数没有任何React依赖,属于纯函数,单元测试时只需mock fetch即可,无需挂载React组件。

// 可单独导出,无任何React依赖,可直接单元测试
export const fetchCityWeather = (city) => {
  const options = {
    method: `POST`,
  };
  return fetch(`/api/weather?city=${city}`, options)
  .then((response) => {
    if(response.ok){
      return response.json()
    }
    throw new Error('Api is not available') 
  })
}

组件中调用逻辑:

import { useState } from 'react'
import { fetchCityWeather } from './对应路径'

export const ForecastButtons = ({ city }) => {
  const [payload, setPayload] = useState(null)
  const [error, setError] = useState(null)
  const [loading, setLoading] = useState(true)
  
  const handleFetch = () => {
    setLoading(true)
    fetchCityWeather(city)
      .then(setPayload)
      .catch(error => {
        console.error('Error fetching data: ', error)
        setError(error)
      })
      .finally(() => setLoading(false))
  }

  const location = payload?.location?.name;
  const currentTemp = payload?.current?.temp_c;

  return(
    <div className="sm:col-span-2">
      <p className="block text-sm font-medium text-gray-700">Select forecast</p>
        <button onClick={handleFetch} className="mt-1 bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded" type='button'>
          Today
        </button>
        <p key={city?.location?.id} className='my-5'>
          { location ? `Current weather in ${location} is ${currentTemp} degrees ` : 'Please search for city to see current weather'}
        </p>
    </div>
  )
}

方案2:抽为自定义Hook(适用于多组件复用请求+状态逻辑)

如果需要在多个组件复用整套请求+状态管理逻辑,可以抽为自定义Hook,要求函数名必须以use开头,且仅能在组件顶层调用:

import { useState } from 'react'
// 自定义Hook,符合Hook调用规则
export const useCityWeather = (city) => {
  const [payload, setPayload] = useState(null)
  const [error, setError] = useState(null)
  const [loading, setLoading] = useState(true)

  const fetchData = () => {
    setLoading(true)
    const options = {
      method: `POST`,
    };
    fetch(`/api/weather?city=${city}`, options)
    .then((response) => {
      if(response.ok){
        return response.json().then(setPayload)
      }
      throw new Error('Api is not available') 
    })
    .catch(error => {
      console.error('Error fetching data: ', error)
      setError(error)
    })
    .finally(() => setLoading(false))
  }

  return { payload, error, loading, fetchData }
}

组件中调用逻辑:

import { useCityWeather } from './对应路径'

export const ForecastButtons = ({ city }) => {
  // 自定义Hook必须在组件顶层调用
  const { payload, fetchData } = useCityWeather(city)

  const location = payload?.location?.name;
  const currentTemp = payload?.current?.temp_c;

  return(
    <div className="sm:col-span-2">
      <p className="block text-sm font-medium text-gray-700">Select forecast</p>
        <button onClick={fetchData} className="mt-1 bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded" type='button'>
          Today
        </button>
        <p key={city?.location?.id} className='my-5'>
          { location ? `Current weather in ${location} is ${currentTemp} degrees ` : 'Please search for city to see current weather'}
        </p>
    </div>
  )
}

自定义Hook可通过React Hooks测试工具完成单元测试,模拟入参、调用触发函数后断言状态变化即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 14:39:05