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

如何使用依赖Context的Hook?解决React钩子调用报错问题

问题描述

通过Context获取用户数据的组件,尝试使用该数据发起API调用时触发以下错误:

Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app

核心矛盾:必须在函数组件顶层调用useAxios Hook,但此时AuthContext的authData尚未加载完成;若在条件判断或useEffect内部调用Hook,则违反React的Hook规则。


相关代码

Portfolio.js

// Portfolio.js
import {Newsfeed} from "../components/Newsfeed";
import {PortfolioList} from "../components/PortfolioList";
import React, {useEffect, useState, useContext} from "react";
import AuthContext from "../context/authContext";
import {useNavigate} from "react-router-dom";
import {useAxios} from "../hooks/useAxios";

export const Portfolio = () => {
    const {authData} = useContext(AuthContext);
    const navigate = useNavigate();
    let coin = null;

    useEffect(() => {
        if (!authData.signedIn) {
            navigate('/login');
        }

        if (authData.signedIn) {
            const { response, loading , error } = useAxios(`/coins/${authData.user.data.coins[0].name}`);
            coin = response;
        }

    }, []);

    if (authData.signedIn) {
        return (
            <main className="-mt-24 pb-8 mb-auto">
                <div className="max-w-3xl mx-auto px-4 sm:px-6 lg:max-w-7xl lg:px-8">
                    <div className="sm:flex sm:items-center">
                        <div className="mb-7 sm:flex-auto">
                            <h1 className="text-xl font-semibold text-indigo-100">Portfolio</h1>
                            <p className="mt-2 text-sm text-gray-200">Welcome to your portfolio.</p>
                        </div>
                    </div>
                    <div className="grid grid-cols-1 gap-4 items-start lg:grid-cols-3 lg:gap-8">
                        {authData.signedIn && authData.user && coin && coin.data(
                            <Newsfeed
                                key={authData.user.data.coins[0].id}
                                coin={coin}
                            />
                        )}
                        <PortfolioList/>
                    </div>
                </div>
            </main>
        );
    }
}

useAxios.js

// useAxios.js
import axios from "axios";
import { useEffect, useState } from "react"

export const useAxios = (param) => {
    const [response, setResponse] = useState(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState('');

    axios.defaults.baseURL = 'https://api.coingecko.com/api/v3';

    const fetchData = async (param) => {
        try {
            setLoading(true);
            const result = await axios(param, { withCredentials: false });
            setResponse(result.data);
        } catch(err) {
            setError(err);
        } finally {
            setLoading(false);
        }
    }

    useEffect(() => {
        fetchData(param);
    }, []);

    return {
        response, loading, error
    }
}

authContext.js

/// authContext.js
import React from "react";

const authData = {
    signedIn: false,
    user: null
};

export default React.createContext({authData: {...authData}, setAuthData: (val) => {}});

解决方案

1. 改造useAxios Hook,支持动态参数与条件触发

修改Hook逻辑,允许空参数时不发起请求,并监听参数变化自动重新请求:

// 改造后的useAxios.js
import axios from "axios";
import { useEffect, useState } from "react"

export const useAxios = (param) => {
    const [response, setResponse] = useState(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState('');

    axios.defaults.baseURL = 'https://api.coingecko.com/api/v3';

    const fetchData = async (currentParam) => {
        if (!currentParam) return; // 参数为空时跳过请求
        try {
            setLoading(true);
            const result = await axios(currentParam, { withCredentials: false });
            setResponse(result.data);
        } catch(err) {
            setError(err);
        } finally {
            setLoading(false);
        }
    }

    useEffect(() => {
        fetchData(param);
    }, [param]); // 监听参数变化,参数更新时自动重新请求

    return {
        response, loading, error
    }
}

2. 调整Portfolio组件逻辑,遵守Hook规则

将useAxios移至组件顶层,通过动态参数控制请求时机,同时完善状态管理与导航逻辑:

// 改造后的Portfolio.js
import {Newsfeed} from "../components/Newsfeed";
import {PortfolioList} from "../components/PortfolioList";
import React, {useEffect, useContext} from "react";
import AuthContext from "../context/authContext";
import {useNavigate} from "react-router-dom";
import {useAxios} from "../hooks/useAxios";

export const Portfolio = () => {
    const {authData} = useContext(AuthContext);
    const navigate = useNavigate();

    // 动态生成请求参数:仅当登录且有用户硬币数据时,传入有效API路径,否则传null
    const requestParam = authData.signedIn && authData.user?.data?.coins[0]?.name 
        ? `/coins/${authData.user.data.coins[0].name}` 
        : null;
    
    // 顶层调用useAxios,遵守Hook规则
    const { response: coin, loading, error } = useAxios(requestParam);

    // 监听登录状态变化,未登录时跳转登录页
    useEffect(() => {
        if (!authData.signedIn) {
            navigate('/login');
        }
    }, [authData.signedIn, navigate]);

    // 未登录时返回空或加载组件
    if (!authData.signedIn) {
        return null;
    }

    return (
        <main className="-mt-24 pb-8 mb-auto">
            <div className="max-w-3xl mx-auto px-4 sm:px-6 lg:max-w-7xl lg:px-8">
                <div className="sm:flex sm:items-center">
                    <div className="mb-7 sm:flex-auto">
                        <h1 className="text-xl font-semibold text-indigo-100">Portfolio</h1>
                        <p className="mt-2 text-sm text-gray-200">Welcome to your portfolio.</p>
                    </div>
                </div>
                <div className="grid grid-cols-1 gap-4 items-start lg:grid-cols-3 lg:gap-8">
                    {loading && <div>加载中...</div>}
                    {error && <div>加载出错:{error.message}</div>}
                    {coin && (
                        <Newsfeed
                            key={authData.user.data.coins[0].id}
                            coin={coin}
                        />
                    )}
                    <PortfolioList/>
                </div>
            </div>
        </main>
    );
}

关键说明

  • 严格遵守Hook规则:所有Hook均在组件顶层调用,确保React能正确跟踪Hook调用顺序
  • 动态请求控制:通过requestParam的空值判断,避免在未获取用户数据时发起无效请求
  • 响应式状态管理:直接使用useAxios返回的response作为硬币数据,确保组件能响应数据变化自动重新渲染
  • 依赖项完善:useEffect添加authData.signedIn和navigate作为依赖,避免过时闭包问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 04:24:33