授权完成后,反应受保护的路由不会重定向到 children

React protected route not redirecting to children when authorisation finished

我正在尝试在我的受保护路由中加入一层身份验证,我一直在使用以下方法进行此操作:

function ProtectedRoute ({ children }) {
    const { user } = useContext(AuthContext);
    
    return user.isVerifying ? (
        <p>Is loading...</p>
    ) : user.verified ? children : <Navigate to="/login" />;
}

export default ProtectedRoute;

我的用户上下文已使用以下信息初始化:

{
    "user": null,
    "verified": false,
    "isVerifying": false
}

目前,用户授权状态由我的 AuthContext.Provider 内部的一个函数 (authUser) 检查,该函数目前在我的组件的每个渲染器上运行(如下所示) .这是一个异步函数,它进行 API 调用以检查存储在 localStorage 中的用户 JWT 令牌是否有效。

import React, { createContext, useEffect, useState } from 'react';
import axios from 'axios';

const AuthContext = createContext();

function AuthProvider({children}) {
    const [user, setUser] = useState({
        "user": null,
        "verified": false,
        "isVerifying": true
    });

    async function authUser() {
        const userLocal = localStorage.getItem('user');

        if (userLocal) {
            const userLocalJSON = JSON.parse(userLocal);
            const verifyResponse = await verifyToken(userLocalJSON);

            if (verifyResponse) {
                setUser({"user": userLocalJSON, "verified": true, "isVerifying": false});
            } else {
                setUser({"user": userLocalJSON, "verified": false, "isVerifying": false});
            }
        }
    }

    async function verifyToken(userJSON) {
        try {
            setUser({"user": null, "verified": false, "isVerifying": true});

            // Make API call

            if (response.status === 200) {
                return true;
            } else {
                return false;
            }
        } catch (e) {
            return false;
        }
    }

    useEffect(() => {
        authUser();
    }, [])

    return (
        <AuthContext.Provider value={{user, authUser}}>
            {children}
        </AuthContext.Provider>
    )
}

export default AuthContext;
export { AuthProvider };

验证令牌后,用户上下文将更新为 verified = true,其中包含有关用户的信息。在此过程中,用户上下文 isVerifying = true(相当于 isLoading)并在 API 调用完成后设置回 false。

现在有一些问题:

  1. 在 DOM 的每个 re-render 上,上下文信息都被设置回其默认值。有没有办法让 useEffect() 只在 localStorage 发生变化时触发?我已经尝试实施以下 solution,它向 localStorage 添加一个侦听器,然后在更改时触发 useEffect。我无法在 localStorage 更改时启动它。也许我做错了什么(见下文)?

     useEffect(() => {
       window.addEventListener('storage', authUser)
    
       return () => {
         window.removeEventListener('storage', authUser)
       }
     }, [])
    
  2. 因为初始 isVerifying = false 和到 true 的切换不会在呈现 DOM 之前发生,所以受保护的路由导航到登录页面。即使最终设置了用户上下文,它仍保留在登录页面上。永远不会显示加载页面,而是立即重定向到登录页面。不幸的是,我最初无法设置 isVerifying = true,因为 non-logged 用户会卡在加载页面上。

任何建议都会很有帮助。

解决问题 2:

你的三元数并没有完全按照你的意愿行事。这有点像说“如果 user.isVerifying !user.verified,导航”。这不是我们想要的。

嵌入式三元组可能很棘手,我通常会尽可能避免使用它们。由于这个组件非常小,实际上您根本不需要使用三元组来执行此操作。我认为这应该有效:

设置 isVerifyingtrue 开始。那么:

function ProtectedRoute ({ children }) {
    const { user } = useContext(AuthContext);

    if (user.isVerifying) {
        return <p>Is loading...</p>
    }

    if (user.verified === false) {
        return <Navigate to="/login" />
    }

    return children

}

export default ProtectedRoute;

编辑:您还需要更新 authUser 中的验证状态。请注意,如果 userLocal 为真,您如何仅更新加载状态。最好的办法是开始加载,并始终在最后调用 setUser。这不是唯一的方法,但我认为它应该有效:

    async function authUser() {
        setUser(currentState => {...currentState, isVerifying: true})
        const userLocal = localStorage.getItem('user');
        
        if (!userLocal) {
            setUser({
                user: null,
                verified: false,
                isVerifying: false,
            })
        }

        const userLocalJSON = JSON.parse(userLocal);
        const verifyResponse = await verifyToken(userLocalJSON);
        setUser({
            user: userLocalJSON,
            verified: verifyResponse,
            isVerifying: false,
        })
    }