reactjs - react : Cannot Login if url entered manually into the browser

标签 reactjs react-router react-dom

我有一个奇怪的问题,您可以导航到登录页面并登录,但如果您尝试在浏览器中手动输入地址,它只会在您每次点击发送时重新加载登录页面。更奇怪的是,它会向您显示仅在首次登录尝试后登录用户可见的导航部分,但它仍会重定向到登录页面。如果我尝试转到仅供登录用户使用的其他页面,则不会显示这些页面。

如果我在浏览器中点击刷新,登录页面将立即重新开始工作,我可以登录了。 我在本地的 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 shouldGoToLoginfalse:

    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/

相关文章:

reactjs - react : Raw HTML tag in content

javascript - Electron 上的 react 路由器无法改变历史

reactjs - 导出 Material ui主题包

javascript - ReactDOM.render() 不工作 - 您可能需要合适的加载器来处理此文件类型

javascript - ReactJS 没有加载/渲染

javascript - 如何在创建 React 组件时为其初始化 Redux 状态?

javascript - 从 react-router-dom 属性 'sumParams' 升级版本 4 useParams () 后 TypeScript 错误在类型 '{}' 上不存在

reactjs - 组件方法代码中的 <element>.innerText 在 enzyme 测试中无法正常工作。使用 Jest+Enzyme(mount()) 测试 React 组件

javascript - 为什么 React Router 会导致 Web 应用程序崩溃?

typescript - 如何在类组件类型脚本中使用 useHistory() 钩子(Hook)