When declare useContext, Error: Invalid hook call. Hooks can only be called inside of the body of a function component
When declare useContext, Error: Invalid hook call. Hooks can only be called inside of the body of a function component
我正在创建将在 LoginPage 上使用的 AuthContext,VSCode 中没有错误,但是当我 运行 应用程序出现如下错误时:
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
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
我已经阅读了文档,我认为我在声明钩子时没有违反任何规则。
我在功能组件的顶层声明了 useContext,我的 react 和 react-dom 版本具有相同的版本。
这是我的 AuthContext:
import { FC, createContext, ReactNode, useReducer } from 'react'
import { AuthReducer } from './Reducer';
export interface IState {
isAuthenticated: boolean
user: string | null
token: string | null
}
// interface for action reducer
export interface IAction {
type: 'LOGIN' | 'LOGOUT' | 'REGISTER' | 'FORGOT PASSWORD'
payload?: any
}
interface IAuthProvider {
children: ReactNode
}
const initialState = {
isAuthenticated: false,
user: null,
token: null
}
interface AuthContextValue {
state: IState;
dispatch: React.Dispatch<IAction>;
}
export const AuthContext = createContext<{ state: IState; dispatch: React.Dispatch<IAction> }>({
state: initialState,
dispatch: (action) => { },
});
export const AuthProvider: FC<IAuthProvider> = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, initialState)
return (
<AuthContext.Provider value={{ state, dispatch }}>
{children}
</AuthContext.Provider>
)
}
这是我的减速器:
import { IAction, IState } from ".";
export const AuthReducer = (state: IState, action: IAction) => {
switch (action.type) {
case 'LOGIN':
localStorage.setItem("user", action.payload.user)
localStorage.setItem("token", action.payload.token)
return {
...state,
isAuthenticated: true,
user: action.payload.user,
token: action.payload.token
}
case 'LOGOUT':
localStorage.clear()
return {
...state,
isAuthenticated: false,
user: null,
token: null
}
default: return state
}
}
这是我声明的登录页面 useContext
:
import React, { FC, useContext } from 'react'
import { AuthContext } from '../AuthContext'
export const Login: FC = () => {
const { state, dispatch } = useContext(AuthContext)
const login = () => {
dispatch({
type: 'LOGIN',
payload: { ...state, isAuthenticated: !state.isAuthenticated }
})
}
return (
<div>
<input name="username" />
<button onClick={login}></button>
</div>
)
}
在我声明 useContext 没有显示错误之前,我到底错过了什么?有人想告诉我吗?谢谢。
已编辑:
我使用的组件 Login
:
import { FC, useState } from 'react'
import { BrowserRouter, Switch, Route } from 'react-router-dom'
import { Dashboard } from '../pages/Dashboard'
import { Login } from '../pages/Login'
import { ProtectedRoute } from './ProtectedRoute'
export const Routes: FC = () => {
return (
<BrowserRouter>
<Switch>
<Route path="/" children={Login} exact />
<ProtectedRoute path="/dashboard" children={Dashboard} exact />
</Switch>
</BrowserRouter>
)
}
你直接在Route
中调用函数Login
。
children={Login}
改为调用组件
children={<Login/>}
或者直接使用
<Route Route path="/" exact />
<Login />
</Route>
Login
和<Login/>
的区别在于Login
只是函数调用,而<Login/>
创建了一个被调用的React组件。 React 组件有自己的上下文。函数调用在调用者上下文中执行。当您查看从您的 JSX 创建的 javascript 时,您可以看到不同之处。您会发现 <Login/>
类似以下内容
React.createElement(Login, props, null);
因此这三种指定路由的方式实际上是相同的:
<Route path="/" children={React.createElement(Login, {}, null)}>
<Route path="/" children={<Login/>}>
<Route path="/" >
<Login/>
</Route>
这是一个可运行的示例,演示了三种实际上相同的方法
const {
createElement,
} = React;
const { HashRouter: Router, Switch, Route, Link } = ReactRouterDOM;
function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/introByAttributeWithCreate">Intro by children attribute with React.createElement</Link>
</li>
<li>
<Link to="/introByAttributeWithJSX">Intro by children attribute with JSX</Link>
</li>
<li>
<Link to="/introByChildJSX">Intro by child JSX</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/introByAttributeWithCreate" children={createElement(Intro, {text: "by attribute with React.createElement"}, null)}/>
<Route path="/introByAttributeWithJSX" children={<Intro text="by attribute with JSX" />}/>
<Route path="/introByChildJSX">
<Intro text="by child JSX" />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return <h2>Home</h2>;
}
function Intro(props) {
return <div><h2>Intro</h2><p>{props.text}</p></div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-router-dom/umd/react-router-dom.min.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<div id="root"></div>
我这里解释的很详细。
我正在创建将在 LoginPage 上使用的 AuthContext,VSCode 中没有错误,但是当我 运行 应用程序出现如下错误时:
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
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
我已经阅读了文档,我认为我在声明钩子时没有违反任何规则。 我在功能组件的顶层声明了 useContext,我的 react 和 react-dom 版本具有相同的版本。
这是我的 AuthContext:
import { FC, createContext, ReactNode, useReducer } from 'react'
import { AuthReducer } from './Reducer';
export interface IState {
isAuthenticated: boolean
user: string | null
token: string | null
}
// interface for action reducer
export interface IAction {
type: 'LOGIN' | 'LOGOUT' | 'REGISTER' | 'FORGOT PASSWORD'
payload?: any
}
interface IAuthProvider {
children: ReactNode
}
const initialState = {
isAuthenticated: false,
user: null,
token: null
}
interface AuthContextValue {
state: IState;
dispatch: React.Dispatch<IAction>;
}
export const AuthContext = createContext<{ state: IState; dispatch: React.Dispatch<IAction> }>({
state: initialState,
dispatch: (action) => { },
});
export const AuthProvider: FC<IAuthProvider> = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, initialState)
return (
<AuthContext.Provider value={{ state, dispatch }}>
{children}
</AuthContext.Provider>
)
}
这是我的减速器:
import { IAction, IState } from ".";
export const AuthReducer = (state: IState, action: IAction) => {
switch (action.type) {
case 'LOGIN':
localStorage.setItem("user", action.payload.user)
localStorage.setItem("token", action.payload.token)
return {
...state,
isAuthenticated: true,
user: action.payload.user,
token: action.payload.token
}
case 'LOGOUT':
localStorage.clear()
return {
...state,
isAuthenticated: false,
user: null,
token: null
}
default: return state
}
}
这是我声明的登录页面 useContext
:
import React, { FC, useContext } from 'react'
import { AuthContext } from '../AuthContext'
export const Login: FC = () => {
const { state, dispatch } = useContext(AuthContext)
const login = () => {
dispatch({
type: 'LOGIN',
payload: { ...state, isAuthenticated: !state.isAuthenticated }
})
}
return (
<div>
<input name="username" />
<button onClick={login}></button>
</div>
)
}
在我声明 useContext 没有显示错误之前,我到底错过了什么?有人想告诉我吗?谢谢。
已编辑:
我使用的组件 Login
:
import { FC, useState } from 'react'
import { BrowserRouter, Switch, Route } from 'react-router-dom'
import { Dashboard } from '../pages/Dashboard'
import { Login } from '../pages/Login'
import { ProtectedRoute } from './ProtectedRoute'
export const Routes: FC = () => {
return (
<BrowserRouter>
<Switch>
<Route path="/" children={Login} exact />
<ProtectedRoute path="/dashboard" children={Dashboard} exact />
</Switch>
</BrowserRouter>
)
}
你直接在Route
中调用函数Login
。
children={Login}
改为调用组件
children={<Login/>}
或者直接使用
<Route Route path="/" exact />
<Login />
</Route>
Login
和<Login/>
的区别在于Login
只是函数调用,而<Login/>
创建了一个被调用的React组件。 React 组件有自己的上下文。函数调用在调用者上下文中执行。当您查看从您的 JSX 创建的 javascript 时,您可以看到不同之处。您会发现 <Login/>
React.createElement(Login, props, null);
因此这三种指定路由的方式实际上是相同的:
<Route path="/" children={React.createElement(Login, {}, null)}>
<Route path="/" children={<Login/>}>
<Route path="/" >
<Login/>
</Route>
这是一个可运行的示例,演示了三种实际上相同的方法
const {
createElement,
} = React;
const { HashRouter: Router, Switch, Route, Link } = ReactRouterDOM;
function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/introByAttributeWithCreate">Intro by children attribute with React.createElement</Link>
</li>
<li>
<Link to="/introByAttributeWithJSX">Intro by children attribute with JSX</Link>
</li>
<li>
<Link to="/introByChildJSX">Intro by child JSX</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/introByAttributeWithCreate" children={createElement(Intro, {text: "by attribute with React.createElement"}, null)}/>
<Route path="/introByAttributeWithJSX" children={<Intro text="by attribute with JSX" />}/>
<Route path="/introByChildJSX">
<Intro text="by child JSX" />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return <h2>Home</h2>;
}
function Intro(props) {
return <div><h2>Intro</h2><p>{props.text}</p></div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-router-dom/umd/react-router-dom.min.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<div id="root"></div>
我这里解释的很详细