由于道具被传递,受保护的路线无法访问

时间:2021-06-30 19:56:55

标签: reactjs

我有一个简单的应用程序,我试图让未经身份验证的用户无法访问它的管理部分。我遇到的问题是下面的 userAuth 始终设置为 false,因此受保护的路由无法访问,即使对于使用适当的 localStorage 身份验证登录的用户也是如此。我的想法是 userAuth 被设置为 false 作为其初始状态,并且当对受保护的路由 URL 发出请求时,useEffect 不会覆盖它。

以下是我目前的路由:

import { lazy, Suspense, useEffect, useState } from 'react';
import {
  BrowserRouter as Router,
  Switch,
  Route
} from "react-router-dom";
import * as ROUTES from './constants/routes';
import ProtectedRoute from './helpers/ProtectedRoute';

const Admin = lazy(() => import('./screens/Admin'));
const Home = lazy(() => import('./screens/Home'));
const Login = lazy(() => import('./screens/Login'));
const NotFound = lazy(() => import('./screens/NotFound'));
const Signup = lazy(() => import('./screens/Signup'));

const App = () => {

  const [userAuth, setUserAuth] = useState(false);

  useEffect(() => { 
    const checkUser = () => {
      const user = localStorage.getItem("userAuth");
      if (user) {
        setUserAuth(true);
      } else {
        setUserAuth(false);
      }
    }
    checkUser();
  }, []);

  return (
    <Router>
      <Suspense fallback={<p>Loading ...</p>}>
        <Switch>
          <Route path={ROUTES.HOME} component={Home} exact={true} />
          <Route userAuth={userAuth} path={ROUTES.LOGIN} component={Login} />
          <Route userAuth={userAuth} path={ROUTES.SIGNUP} component={Signup} />
          <ProtectedRoute userAuth={userAuth} path={ROUTES.ADMIN} exact>
            <Admin />
          </ProtectedRoute>
          <Route component={NotFound} />
        </Switch>
      </Suspense>
    </Router>
  );
}

export default App;

我有一个 useEffect 来判断用户是否已通过身份验证。这是在用户为登录页面提交成功的 POST 请求时设置的:

import { useEffect, useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import Header from '../components/Header';
import * as ROUTES from '../constants/routes';

const Login = ({setUserAuth}) => {

    const history = useHistory();

    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');
    const [loginErr, setLoginErr] = useState(false);
    
    const isUsernameValid = username.length > 0;
    const isPasswordValid = (password !== '' && password.length > 5);
    const isLoginValid = isUsernameValid && isPasswordValid;

    useEffect(() => {
        document.title = 'Login';
    }, []);

    const logIn = async (e, username, password) => {

        e.preventDefault();

        const data = {username, password}
        const formData = JSON.stringify(data);

        try {
          const req = await fetch(
            "http://localhost:3000/api/login",
            {
              method: "POST",
              body: formData,
              headers: {
                Accept: "application/json",
                "Content-Type": "application/json",
              },
            }
          );
          const myJson = await req.json();
          if (req.status !== 200) {
            setLoginErr(true);
            return;
          }
          localStorage.setItem("token", myJson.token);
          localStorage.setItem("userAuth", true);
          setUserAuth(true);
        } catch (err) {
          setLoginErr(true);
        }
    }

    return (
        <div className="bg-gray-100">
            <div className="container h-screen mx-auto flex flex-col flex-wrap">
                <div className="container border rounded m-auto w-1/4 flex flex-wrap justify-center bg-white shadow-new">
                    <h1 className="text-3xl m-4">Blog.</h1>
                    <form className="flex flex-wrap justify-center" onSubmit={(e) => logIn(e, username, password)}>
                        <input 
                            className="border rounded w-9/12 p-1 mb-2 pl-2 bg-gray-100 outline-none" 
                            name="username"
                            placeholder="Username"
                            value={username}
                            onChange={(e) => setUsername(e.target.value)}
                        />
                        <input 
                            className="border rounded w-9/12 p-1 mb-4 pl-2 bg-gray-100 outline-none" 
                            name="password"
                            placeholder="Password"
                            type="password"
                            value={password}
                            onChange={(e) => setPassword(e.target.value)}
                        />
                        <button 
                            className={`border rounded w-9/12 p-1 mb-4 bg-blue-400 text-white font-medium ${isLoginValid ? "cursor-pointer" : "bg-opacity-50 cursor-default"}`}
                            type="submit"
                        >Log In</button>
                        <div className="mb-4">
                            <p>Don't have an account? <Link to={ROUTES.SIGNUP} className="font-medium text-blue-500 cursor-pointer">Sign up</Link></p>
                        </div>
                        {loginErr && <p className="text-xs text-red-600">Error with login information</p>}
                    </form>
                </div>
            </div>    
        </div>
    )

}

export default Login;

这是受保护的路由:

import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import * as ROUTES from '../constants/routes';

const ProtectedRoute = ({ userAuth, children, ...rest }) => {

    return (
        <Route
            {...rest}
            render={({ location }) => {
                if (userAuth && userAuth !== null) {
                    console.log(userAuth)
                    console.log("FIRST")
                    // Using cloneElement to add / modify the props of its children.
                    return React.cloneElement(children, { userAuth });
                }
                
                if (!userAuth || userAuth === null) {
                    console.log(userAuth)
                    console.log("SECOND")
                    return (
                        <Redirect
                            to={{
                                pathname: ROUTES.LOGIN,
                                state: { from: location }
                            }}
                        />
                    );
                }

                return null;
            }}
        />
    )
}

export default ProtectedRoute;

任何建议将不胜感激。

非常感谢!

2 个答案:

答案 0 :(得分:1)

问题

问题似乎是 Login 组件没有接收 setUserAuth 作为道具,因为它是由 Route 道具上的标准 component 组件呈现的,这意味着它只是通过路由道具。

解决方案

Login 道具上渲染 render 并传入额外的道具。这将允许登录页面更新私有路由的父组件中的 userAuth 状态。

<Route
  path={ROUTES.LOGIN}
  render={routeProps => (
    <Login
      {...routeProps}
      setUserAuth={setUserAuth}
    />
  )}
/>

移除挂载 useEffect 钩子并使用状态初始化函数从 localStorage 设置初始 userAuth 状态,使其在初始渲染时可用。

const [userAuth, setUserAuth] = useState(() => {
  const user = localStorage.getItem("userAuth");
  return !!user;
});

答案 1 :(得分:0)

您的 useEffect 钩子检查用户是否仅在组件安装时登录。您应该将 const user = localStorage.getItem("userAuth") 移到钩子之外,然后在钩子的依赖数组中添加 user,这样代码就会在依赖项的每次更改时运行(这是用户成功登录后会发生的情况)在)

 const user = localStorage.getItem("userAuth");

 useEffect(() => { 
    const checkUser = () => {
      if (user) {
        setUserAuth(true);
      } else {
        setUserAuth(false);
      }
    }
    checkUser();
  }, [user]);