我有一个奇怪的问题,您可以导航到登录页面并登录,但如果您尝试在浏览器中手动输入地址,它只会在您每次点击发送时重新加载登录页面。更奇怪的是,它会向您显示仅在首次登录尝试后登录用户可见的导航部分,但它仍会重定向到登录页面。如果我尝试转到仅供登录用户使用的其他页面,则不会显示这些页面。
如果我在浏览器中点击刷新,登录页面将立即重新开始工作,我可以登录了。 我在本地的 docker 容器上使用 React Router。
有什么办法可以解决这个问题吗?
这就是我的 React 方面的样子:
App.js
import React, { useContext } from "react";
import {
BrowserRouter as Router,
Routes,
Route,
Navigate
} from "react-router-dom";
import { AuthContext, AuthContextProvider } from './contexts/AuthContext'
import { FacilityDetail } from './components/FacilityDetail'
import { Settings } from './components/Settings'
import { Login } from './components/Login'
import { Reset } from './components/Reset'
import { Navbar } from "./components/Navbar";
import { FacilityUpdate } from "./components/FacilityUpdate";
import { Signup } from "./components/Signup"
import { ConfirmEmail } from "./components/ConfirmEmail";
import { FacilityList } from './components/FacilityList'
import { ResetConfirm } from './components/ResetConfirm'
import { Home } from "./components/Home";
const EnforceAuthOnRoute = ({ children }) => {
const { shouldGoToLogin, user } = useContext(AuthContext)
return user && !shouldGoToLogin ? children : <Navigate replace to="/login" />
}
export default function App() {
return (
<Router>
<AuthContextProvider>
<div>
<Navbar />
{/* A <Routes> looks through its children <Route>s and
renders the first one that matches the current URL. */}
<div className="max-w-8xl mx-auto px-4 sm:px-6 md:px-8">
<Routes>
<Route path="/about" element={<About/>} />
<Route path="/users" element={<Users />} />
<Route path="/facilities/:id" element={<EnforceAuthOnRoute><FacilityDetail /></EnforceAuthOnRoute>} exact />
<Route path="/facilities/:id/update" element={<EnforceAuthOnRoute><FacilityUpdate /></EnforceAuthOnRoute>} exact />
<Route path="/settings" element={<EnforceAuthOnRoute><Settings /></EnforceAuthOnRoute>} exact />
<Route path="/login" element={<Login />} exact />
<Route path="/signup" element={<Signup />} exact />
<Route path="/reset" element={<Reset />} exact />
<Route path="/password-reset/confirm/:uid/:token" element={<ResetConfirm />} exact />
<Route path="/accounts/confirm-email/:key" element={<ConfirmEmail />} exact />
<Route path="/facilities" element={<EnforceAuthOnRoute><FacilityList /></EnforceAuthOnRoute>} exact />
<Route path="/" element={<Home />} exact />
</Routes>
</div>
</div>
</AuthContextProvider>
</Router>
);
}
function About() {
return <h2>About</h2>;
}
function Users() {
return <h2>Users</h2>;
}
登录.js
import { useContext, useState } from 'react';
import { Formik, Field, Form } from 'formik';
import { useNavigate } from "react-router-dom"
import { AuthContext } from '../contexts/AuthContext'
export function Login() {
const [loading, setLoading] = useState(false)
const { login } = useContext(AuthContext)
const navigate = useNavigate()
function handleSubmit(values) {
setLoading(true)
login(values).then(() => {
navigate('/facilities')
}).finally(() => setLoading(false))
}
return (
<div>
{loading && "Loading..."}
<Formik
initialValues={{
email: '',
password: '',
}}
onSubmit={handleSubmit}>
{({ errors, touched }) => (
<Form>
<Field name="email">
{({ field, form }) => (
<label className="mt-3 block">
<span className="text-gray-700">Email</span>
<input
{...field}
type="text"
className="
mt-1
block
w-full
rounded-md
border-gray-300
shadow-sm
focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50
"
placeholder=""
style={
form.touched.email && form.errors.email ? (
{ border: '2px solid var(--primary-red)'}
) : null
}
/>
</label>
)}
</Field>
<Field name="password">
{({ field, form }) => (
<label className="mt-3 block">
<span className="text-gray-700">Password</span>
<input
{...field}
type="password"
className="
mt-1
block
w-full
rounded-md
border-gray-300
shadow-sm
focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50
"
placeholder=""
style={
form.touched.password && form.errors.password ? (
{ border: '2px solid var(--primary-red)'}
) : null
}
/>
</label>
)}
</Field>
<button className="btn btn-gr"
type="submit">
Submit
</button>
</Form>
)}
</Formik>
</div>
)
}
AuthContext.js
import React, { useEffect, useState } from 'react'
import { API } from "../api"
import axios from "axios"
import { isAfter, isEqual, parseISO, sub } from 'date-fns'
export const AuthContext = React.createContext(null)
export function AuthContextProvider({ children }) {
const [accessTokenExpiration, setAccessTokenExpiraton] = useState(undefined);
const getUser = () => {
return JSON.parse(localStorage.getItem('user'))
}
const isLoggedIn = () => {
return localStorage.getItem('user') !== null
}
const [user, setUser] = useState(() => {
return isLoggedIn() ? getUser() : null;
})
const [shouldGoToLogin, setShouldGoToLogin] = useState(() => {
if (!user || !user.access_token || !user.refresh_token) {
return true;
}
return false;
})
const logout = async () => {
if (!user) {
return;
}
const { access_token } = user;
localStorage.removeItem('user')
setUser(null);
return axios.post(API.auth.logout, {
headers: {
"Authorization": `Bearer ${access_token}`,
"Content-Type": "application/json"
},
withCredentials: true
});
}
const login = async (values) => {
console.log(values);
const correctedValues = { ...values, username: values.email };
return axios.post(API.auth.login, correctedValues)
.then(res => {
const data = res.data;
processApiData(data);
})
}
const refreshToken = async () => {
const user = getUser();
const redirectToLogout = () => {
localStorage.clear(); // Clear our localStorage
setShouldGoToLogin(true);
};
if (!user) { // No user
redirectToLogout();
}
console.log(API.auth.refreshToken);
const resp = await fetch(API.auth.refreshToken, {
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({'refresh': user?.refresh_token}),
method: "POST",
withCredentials: true
})
console.log("status", resp.status);
if (resp.status === 200) {
const data = await resp.json(); // Convert to JSON
console.log("refresh token data", data);
processApiData(data);
} else {
redirectToLogout();
}
}
const resetPassword = async (values) => {
return axios.post(API.auth.passwordReset, values);
}
const processApiData = (resp) => {
let newUser = { ...user, ...resp };
delete(newUser.user); // Delete the user sub-object since we merged that directly into the top-level object
saveUser(newUser); // Save the user
const { access_token_expiration } = newUser;
if (access_token_expiration) {
console.log("have expiration", access_token_expiration);
const nextExpiration = parseISO(access_token_expiration); // Convert from ISO 8601 to a Date Object
const earlyRefreshTime = sub(nextExpiration, { minutes: 55 }); // Do an hourish early
setAccessTokenExpiraton(earlyRefreshTime); // Set the upcoming expiraton
}
}
const saveUser = async (newUser) => {
localStorage.setItem('user', JSON.stringify(newUser))
setUser(newUser)
}
const signup = async (values) => {
return axios.post(API.auth.signup, values);
}
useEffect(() => {
if (!user) {
return;
}
const interval = setInterval(()=> {
if(!user){
return false;
}
if (accessTokenExpiration) {
const now = new Date(); // Get the current time
console.log(now);
console.log(accessTokenExpiration);
if (isAfter(now, accessTokenExpiration) || isEqual(now, accessTokenExpiration)) { // If we are late to the party or the stars have aligned
refreshToken(); // Refresh the token
}
} else { // We do not have an access token expiration yet
refreshToken(); // Refresh the token immediately so we get a time
}
}, 1000 * 15)
return ()=> clearInterval(interval)
}, [accessTokenExpiration, refreshToken, user])
return (
<AuthContext.Provider value={{
getUser,
isLoggedIn,
logout,
login,
resetPassword,
signup,
user,
shouldGoToLogin
}}>
{children}
</AuthContext.Provider>
)
}
最佳答案
我的直觉是,您在 AuthContext.js
中的多个地方设置了 shouldGoToLogin
状态,但这些地方混淆了。
这可能会导致 EnforceAuthOnRoute
函数在 shouldGoToLogin
设置为 false 之前运行。
尝试将默认设置简单地设置为 true
并处理当它在其他地方设置为 false 时的逻辑:
const [shouldGoToLogin, setShouldGoToLogin] = useState(true);
或者,在调用 navigate()
之前尝试让 handleSubmit
await shouldGoToLogin
为 false
:
const { shouldGoToLogin } = useContext(AuthContext)
useEffect(() => {
if(shouldGoToLogin) navigate('/facilities');
},[shouldGoToLogin])
function handleSubmit(values) {
setLoading(true)
login(values).finally(() => setLoading(false))
关于reactjs - react : Cannot Login if url entered manually into the browser,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72391595/