如何让 AuthProvider 中的 useEffect() 先运行再调用 useContext()
How to make useEffect() in AuthProvider runs first then call useContext()
我在我的项目中使用 React,但我在获取用户会话方面遇到了问题。提供者内部的每个组件都可以从这里调用。当浏览器开始加载页面时,user
状态始终为空,直到 axios 在 useEffect() 中完成用户会话。如何控制必须先调用 useEffect() 的 AuthProvider,然后用户状态才能在任何子组件中使用。
这是我的 Auth Context 实现。
import axios from "axios";
import { createContext, useContext, useEffect, useState } from "react"
// Create context to make a context reference.
const authContext = createContext()
// Provider component that warp your app and make auth object
// available to any child component that calls useAuth()
export function AuthProvider({children}) {
const [user,setUser] = useState(null)
const URL_API = "http://localhost:5000/auth"
useEffect(()=>{
axios.get(URL_API + "/login")
.then((response)=>{
if (response.data.loggedIn === true) {
setUser(response.data.user)
}
else {
setUser(null)
}
console.log(response.data.user)
})
},[])
const login = async (email,password) => {
const response = await axios.post(URL_API + "/login", { email, password });
if (response.data.err) {
throw response.data.err
}
if (response.data.loggedIn === true) {
setUser(response.data.user);
}
else {
setUser(null);
}
return response.data.user;
}
const register = async (firstname,lastname,email,password) => {
const response = await axios.post(URL_API + "/register", { firstname, lastname, email, password });
if (response.data.err) {
throw response.data.err
}
if (response.data.loggedIn === true) {
setUser(response.data.user);
}
else {
setUser(null);
}
return response.data.user;
}
const logout = async () => {
const response = await axios.get(URL_API + "/logout");
if (response.data.err) {
throw response.data.err
}
if (response.data.loggedIn === true) {
console.log("Something went wrong!");
}
setUser(null);
}
return (
<authContext.Provider value={{user,login,register,logout}}>
{children}
</authContext.Provider>
)
}
// Hook for child components to get the auth object
// and re-render when it changes.
export const useAuth = () => {
return useContext(authContext)
}
如果 auth.user 有一个对象但由于 auth.user 为空而未定义,我在 PublicRoute 中调用了 useAuth() 进行路由。
import { Navigate, Outlet, useLocation } from "react-router-dom"
import { useAuth } from "../Contexts/Auth"
function PublicRoute({firstPath = "/login",redirectPath = "/home",children}) {
const auth = useAuth()
const location = useLocation()
return ( auth.user ? <Navigate to={redirectPath}/> : location.pathname === "/" ? <Navigate to={firstPath}/> : children ? children : <Outlet/> )
}
export default PublicRoute
我正在使用 AuthProvider 覆盖每个 React Router DOM 元素以确保每个路由都可以使用上下文。
这是我的 App.js 用于控制 AuthProvider 与 React Router。
import './App.css';
import axios from 'axios';
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate, Outlet } from "react-router-dom"
import Navigator from './Components/Navigator';
import Header from './Components/Header';
import Home from './Page/Home';
import Dashboard from './Page/Dashboard';
import NotFound from './Page/NotFound';
import Login from "./Page/Login"
import Logout from './Page/Logout';
import Register from "./Page/Register"
import ForgotPassword from './Page/ForgotPassword';
import PublicRoute from './Routes/PublicRoute';
import ProtectedRoute from './Routes/ProtectedRoute';
import { SnackbarProvider } from "notistack"
import { AuthProvider } from './Contexts/Auth';
function App() {
axios.defaults.withCredentials = true
return (
<SnackbarProvider maxSnack={3}>
<AuthProvider>
<Router>
<Routes>
<Route path="/" element={<PublicRoute/>}>
<Route element={<><Header/><Outlet/></>}>
<Route path="/register" element={<Register/>}/>
<Route path='/forgot_password' element={<ForgotPassword/>}/>
</Route>
</Route>
<Route element={<ProtectedRoute roles={["user","admin"]}/>}>
<Route element={<><Navigator/><Outlet/></>}>
<Route path="/home" index element={<Home/>}/>
<Route path="/dashboard" element={<Dashboard/>}/>
<Route path="/logout" element={<Logout/>}/>
</Route>
</Route>
<Route path="/login" element={<Login/>}/>
<Route path="*" element={<Navigate to="404"/>}/>
<Route path="/404" element={<NotFound/>}/>
</Routes>
</Router>
</AuthProvider>
</SnackbarProvider>
);
}
export default App;
Here is console log in the browser that PublicRoute is useAuth() before the useEffect() called
我猜你的初始 user
状态和 PublicRoute
组件重定向 before[=26 有问题=] 该应用 实际上 computed/resolved 用户的身份验证状态。
问题是您的 resolved/unauthenticated 状态被您的初始状态值掩盖了。您可能希望使用不确定的初始状态并有条件地呈现 null 或某些加载指示器,直到身份验证状态得到解决。
示例:
export function AuthProvider({ children }) {
const [user,setUser] = useState(); // <-- initially undefined
const URL_API = "http://localhost:5000/auth"
useEffect(() => {
axios.get(URL_API + "/login")
.then((response) => {
if (response.data.loggedIn) {
setUser(response.data.user);
} else {
setUser(null);
}
console.log(response.data.user);
});
},[]);
...
return (
<authContext.Provider value={{ user, login, register, logout }}>
{children}
</authContext.Provider>
)
}
公共路线
function PublicRoute({ firstPath = "/login", redirectPath = "/home", children }) {
const auth = useAuth();
const location = useLocation();
if (auth.user === undefined) {
return null; // or loading indicator, etc...
}
return auth.user
? <Navigate to={redirectPath} />
: location.pathname === "/"
? <Navigate to={firstPath} />
: children ? children : <Outlet />;
}
我在我的项目中使用 React,但我在获取用户会话方面遇到了问题。提供者内部的每个组件都可以从这里调用。当浏览器开始加载页面时,user
状态始终为空,直到 axios 在 useEffect() 中完成用户会话。如何控制必须先调用 useEffect() 的 AuthProvider,然后用户状态才能在任何子组件中使用。
这是我的 Auth Context 实现。
import axios from "axios";
import { createContext, useContext, useEffect, useState } from "react"
// Create context to make a context reference.
const authContext = createContext()
// Provider component that warp your app and make auth object
// available to any child component that calls useAuth()
export function AuthProvider({children}) {
const [user,setUser] = useState(null)
const URL_API = "http://localhost:5000/auth"
useEffect(()=>{
axios.get(URL_API + "/login")
.then((response)=>{
if (response.data.loggedIn === true) {
setUser(response.data.user)
}
else {
setUser(null)
}
console.log(response.data.user)
})
},[])
const login = async (email,password) => {
const response = await axios.post(URL_API + "/login", { email, password });
if (response.data.err) {
throw response.data.err
}
if (response.data.loggedIn === true) {
setUser(response.data.user);
}
else {
setUser(null);
}
return response.data.user;
}
const register = async (firstname,lastname,email,password) => {
const response = await axios.post(URL_API + "/register", { firstname, lastname, email, password });
if (response.data.err) {
throw response.data.err
}
if (response.data.loggedIn === true) {
setUser(response.data.user);
}
else {
setUser(null);
}
return response.data.user;
}
const logout = async () => {
const response = await axios.get(URL_API + "/logout");
if (response.data.err) {
throw response.data.err
}
if (response.data.loggedIn === true) {
console.log("Something went wrong!");
}
setUser(null);
}
return (
<authContext.Provider value={{user,login,register,logout}}>
{children}
</authContext.Provider>
)
}
// Hook for child components to get the auth object
// and re-render when it changes.
export const useAuth = () => {
return useContext(authContext)
}
如果 auth.user 有一个对象但由于 auth.user 为空而未定义,我在 PublicRoute 中调用了 useAuth() 进行路由。
import { Navigate, Outlet, useLocation } from "react-router-dom"
import { useAuth } from "../Contexts/Auth"
function PublicRoute({firstPath = "/login",redirectPath = "/home",children}) {
const auth = useAuth()
const location = useLocation()
return ( auth.user ? <Navigate to={redirectPath}/> : location.pathname === "/" ? <Navigate to={firstPath}/> : children ? children : <Outlet/> )
}
export default PublicRoute
我正在使用 AuthProvider 覆盖每个 React Router DOM 元素以确保每个路由都可以使用上下文。
这是我的 App.js 用于控制 AuthProvider 与 React Router。
import './App.css';
import axios from 'axios';
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate, Outlet } from "react-router-dom"
import Navigator from './Components/Navigator';
import Header from './Components/Header';
import Home from './Page/Home';
import Dashboard from './Page/Dashboard';
import NotFound from './Page/NotFound';
import Login from "./Page/Login"
import Logout from './Page/Logout';
import Register from "./Page/Register"
import ForgotPassword from './Page/ForgotPassword';
import PublicRoute from './Routes/PublicRoute';
import ProtectedRoute from './Routes/ProtectedRoute';
import { SnackbarProvider } from "notistack"
import { AuthProvider } from './Contexts/Auth';
function App() {
axios.defaults.withCredentials = true
return (
<SnackbarProvider maxSnack={3}>
<AuthProvider>
<Router>
<Routes>
<Route path="/" element={<PublicRoute/>}>
<Route element={<><Header/><Outlet/></>}>
<Route path="/register" element={<Register/>}/>
<Route path='/forgot_password' element={<ForgotPassword/>}/>
</Route>
</Route>
<Route element={<ProtectedRoute roles={["user","admin"]}/>}>
<Route element={<><Navigator/><Outlet/></>}>
<Route path="/home" index element={<Home/>}/>
<Route path="/dashboard" element={<Dashboard/>}/>
<Route path="/logout" element={<Logout/>}/>
</Route>
</Route>
<Route path="/login" element={<Login/>}/>
<Route path="*" element={<Navigate to="404"/>}/>
<Route path="/404" element={<NotFound/>}/>
</Routes>
</Router>
</AuthProvider>
</SnackbarProvider>
);
}
export default App;
Here is console log in the browser that PublicRoute is useAuth() before the useEffect() called
我猜你的初始 user
状态和 PublicRoute
组件重定向 before[=26 有问题=] 该应用 实际上 computed/resolved 用户的身份验证状态。
问题是您的 resolved/unauthenticated 状态被您的初始状态值掩盖了。您可能希望使用不确定的初始状态并有条件地呈现 null 或某些加载指示器,直到身份验证状态得到解决。
示例:
export function AuthProvider({ children }) {
const [user,setUser] = useState(); // <-- initially undefined
const URL_API = "http://localhost:5000/auth"
useEffect(() => {
axios.get(URL_API + "/login")
.then((response) => {
if (response.data.loggedIn) {
setUser(response.data.user);
} else {
setUser(null);
}
console.log(response.data.user);
});
},[]);
...
return (
<authContext.Provider value={{ user, login, register, logout }}>
{children}
</authContext.Provider>
)
}
公共路线
function PublicRoute({ firstPath = "/login", redirectPath = "/home", children }) {
const auth = useAuth();
const location = useLocation();
if (auth.user === undefined) {
return null; // or loading indicator, etc...
}
return auth.user
? <Navigate to={redirectPath} />
: location.pathname === "/"
? <Navigate to={firstPath} />
: children ? children : <Outlet />;
}