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

React Invalid Hook错误:无法通过Context Provider传递setState函数

React Context更新全局状态时触发Invalid Hook Call错误

我是React新手,尝试通过Context Provider创建全局用户状态上下文,期望登录表单提交成功后,用接口返回的用户名和角色更新全局状态,但调用更新方法时触发了钩子调用错误,疑惑为什么同一个位置的两个自定义钩子一个正常一个报错。


相关代码

App组件

import {BrowserRouter,Routes,Route} from 'react-router-dom';

//PAGES AND COMPONENTS
import Registration from './components/registration/Registration';
import Home from './components/home/Home';
import Login from './components/login/Login';

//GLOBAL CONTEXTS
import {AuthProvider} from './context/AuthContext'


function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <AuthProvider>  
          <Routes>
              <Route path='/' element={<Home/>}></Route>
              <Route path='/Register' element={<Registration/>}></Route>
              <Route path='/Login' element={<Login/>}></Route>
          </Routes>
        </AuthProvider>
      </BrowserRouter>
    </div>
  );
}

export default App;

AuthContext.js

import React, { useState, useContext } from 'react';

const AuthContext= React.createContext();
const AuthUpdateContext = React.createContext();

export function useAuth(){
    return useContext(AuthContext);
}

export function useAuthUpdate(){
    return useContext(AuthUpdateContext);
}

export function AuthProvider({children}){
    const [authState,setAuthState]=useState({
                                                id:0,
                                                username:"",
                                                role:"user"
                                            });

    return(
        <AuthContext.Provider value={authState}>
            <AuthUpdateContext.Provider value={setAuthState}>
                {children}
            </AuthUpdateContext.Provider>    
        </AuthContext.Provider>
    )
};

Login组件

import {useNavigate} from 'react-router-dom'
import {Formik, Form, Field,ErrorMessage} from 'formik'
import axios from 'axios';
import * as Yup from 'yup';
import './Login.css';
import {useAuth,useAuthUpdate} from '../../context/AuthContext'
import { useState } from 'react';


function Login() {
  const navigate=useNavigate();
  const [invalidCredentials,setInvalidCredentials]=useState("");
  const setAuthState=useAuthUpdate;
  const useAuthState=useAuth();

  
  const onSubmit=(data)=>{
      axios
            .post('http://localhost:3001/User/Login',data)
            .catch((error)=>{
              setInvalidCredentials("Invalid Credentials. Please try again.")
            })
            .then((response)=>{
              localStorage.setItem("token",response.data.token);
              setAuthState((prev)=>({
                ...prev,
                id:response.data.id,
                username:response.data.username,
                role:response.data.role
              }))
            })
            .then(()=>{
              console.log(useAuthState);
              navigate('/');
            })
  }

  const validationSchema=Yup.object().shape({
    usernameOrEmail:Yup
                .string()
                .required('Field must not be blank.'),
    password:Yup.string()
                .required('Field must not be blank.'),
});

  const initialValues ={
    usernameOrEmail:"",
    password:"",
        };

  return (
    <div className='container'>
      <div className='row'>
        <h1>Welcome Back!</h1>
      </div>

      <div className='row'>
        <h2>Log-in Here:</h2>
      </div>

      <Formik onSubmit={onSubmit} validationSchema={validationSchema} initialValues={initialValues}>
        <Form>
          <div className='row'>
            <label>Username/Email</label>
            <Field name='usernameOrEmail'></Field>
            <ErrorMessage name="usernameOrEmail">
                                    {msg=><div className="errorMsg">{msg}</div>}
            </ErrorMessage>
          </div>
          <div className='row'>
            <label>Password</label>
            <Field  name='password'
                    type='password'></Field>
            <ErrorMessage name="password">
                                    {msg=><div className="errorMsg">{msg}</div>}
            </ErrorMessage>
          </div>
          
          <div id="register-btn"className="row">
                    <button className="btn btn-secondary" type="submit" >Login</button>
          </div>

          <div className='row'>
            <span className='errorMsg'>{invalidCredentials}</span>
          </div>
        </Form>
      </Formik>
    </div>
  )
}

export default Login

触发的错误

Uncaught (in promise) 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
    at Object.throwInvalidHookError (react-dom.development.js:16227:1)
    at useContext (react.development.js:1618:1)
    at useAuthUpdate(AuthContext.js:12:1)
    at Login.js:25:1

疑惑点

useAuthState和setAuthState都是React Hook且调用位置相同,为何前者正常后者触发错误?


解决方案

问题出在Login组件的这一行:

const setAuthState=useAuthUpdate;

你没有调用自定义钩子useAuthUpdate,只是把钩子函数本身赋值给了setAuthState。当你在onSubmit的回调里调用setAuthState时,本质上是在嵌套函数中调用了useAuthUpdate这个钩子,违反了钩子必须在组件顶层调用的规则。而useAuth()是在组件顶层正确调用的,所以没有报错。

修正步骤

  1. 调用useAuthUpdate钩子,获取状态更新函数:
const setAuthState = useAuthUpdate(); // 加上()调用钩子
  1. 重命名变量避免混淆(可选但推荐):
const authState = useAuth(); // 替换原useAuthState,明确这是状态值而非钩子

修正后的Login组件相关代码片段:

function Login() {
  const navigate=useNavigate();
  const [invalidCredentials,setInvalidCredentials]=useState("");
  const setAuthState = useAuthUpdate(); // 修复钩子调用
  const authState = useAuth(); // 重命名变量
  
  // ... 其余代码不变
}

同时,确保所有用到React钩子的文件都导入了对应的API,避免潜在错误:

  • AuthContext.js顶部添加import React, { useState, useContext } from 'react';
  • Login.js顶部添加import { useState } from 'react';

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 22:35:20