Reputation: 137
I'm creating an AuthContext that will be used on the LoginPage, there is no error in VSCode but when I run the application it gives an error like the following:
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.
I've read the documentation and I don't think there's any rule I'm breaking in declaring hooks. I declare useContext at the top level inside functional component, my react dan react-dom version is have the same version.
This is my 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>
)
}
This is my reducer:
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
}
}
And this my login page which I declared 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>
)
}
Before I declare useContext isn't showing error, what exactly am I missing? Anyone want to tell me? Thank you.
EDITED:
Component where I'm use 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>
)
}
Upvotes: 1
Views: 7321
Reputation: 51333
You call the function Login
directly in the Route
.
children={Login}
Instead call the component
children={<Login/>}
or simply use
<Route Route path="/" exact />
<Login />
</Route>
The difference between Login
and <Login/>
is that Login
is just the function call while <Login/>
creates a React component that is called. A react component has an own context. A function call is executed in the callers context. You can see the difference when you look at the javascript that is created from your JSX. You will find something like the following for <Login/>
React.createElement(Login, props, null);
Thus this three ways to specify a route are effectively the same:
<Route path="/" children={React.createElement(Login, {}, null)}>
<Route path="/" children={<Login/>}>
<Route path="/" >
<Login/>
</Route>
Here is a runnable example that demonstrates the three ways that are effectively the same
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>
I gave a detailed explanation here https://stackoverflow.com/a/69082513/974186.
Upvotes: 1