如何在没有 AuthContext 的情况下创建私有路由

Hit*_* DD 1 reactjs react-router firebase-authentication react-router-dom

我正在尝试创建从登录和注册页面到仪表板页面的私有路由,但是我偶然发现的所有教程和指南都需要某种 AuthContext 事物,并且我没有使用 AuthContext 实现我的身份验证过程。
我尝试了不同的方法,但都不起作用,并且当我到达仪表板页面时最终给了我一个空白页面,我该怎么做才能使其成为私有路线?顺便说一句,使用 Firebase v9。

注册.js

import './Signup.css'
import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { auth }from '../../../firebase';
import { createUserWithEmailAndPassword, onAuthStateChanged } from 'firebase/auth';
import { Typography, Container, TextField, Button, Alert } from '@mui/material';

const Signup = () => {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [confirmPassword, setConfirmPassword] = useState('');
    const [error, setError] = useState('');
    const [user, setUser] = useState({});
    const history = useHistory();

    onAuthStateChanged(auth, (currentUser) => {
        setUser(currentUser);
    })
    
    const signup = async (e) => {
        e.preventDefault();

        if (password !== confirmPassword) {
            return setError("Passwords do not match")
        }

        try {
            const user = await createUserWithEmailAndPassword(
                auth, 
                email,
                password
            );
            history.push("/dashboard/canvas");
        } catch (err) {
            setError(err.message);
        }
    }

    return (
        <>
            <div className="text-div">
                <Typography textAlign="center" variant="h3">Create a new account</Typography>
            </div>
            
            <Container className="cont" maxWidth="xl" sx={{ backgroundColor: "#ffffff", width: 500, height: "auto", borderRadius: 4, marginTop: 5, display: "flex", flexDirection: "column", padding: 5, }}>
                { error && <Alert severity="error">{error}</Alert> }
                <TextField label="Email" margin="dense" type="email" onChange={ (e) => {
                    setEmail(e.target.value);
                }}/>

                <TextField label="Password" margin="dense" type="password" onChange={ (e) => {
                    setPassword(e.target.value);
                }}/>

                <TextField label="Confirm Password" margin="dense" type="password" onChange={ (e) => {
                    setConfirmPassword(e.target.value);
                }}/>
                <Button onClick={signup} variant="contained" sx={{ marginTop: 2, }}>Sign Up</Button>

                <div>
                    Already have an account? <Link to="/login" style={{ color: '#000' }}>Log In</Link>
                </div>
            </Container>
        </>
    )
}

export default Signup;

Run Code Online (Sandbox Code Playgroud)

登录.js

import './Login.css'
import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { signInWithEmailAndPassword } from 'firebase/auth';
import { auth }from '../../../firebase';
import { Typography, Container, TextField, Button, Alert } from '@mui/material';


const Login = () => {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [error, setError] = useState('');
    const history = useHistory();

    const login = async () => {
        try {
            const user = await signInWithEmailAndPassword(
                auth, 
                email,
                password
            );
            //alert("Success, user is recognized");
            history.push("/dashboard/canvas");
        } catch (err) {
            setError("The email or password you entered is incorrect");
        }
    }

    return (
        <>
            <div className="text-div">
                <Typography textAlign="center" variant="h3">Login</Typography>
            </div>
            
            <Container className="cont" maxWidth="xl" sx={{ backgroundColor: "#ffffff", width: 500, height: "auto", borderRadius: 4, marginTop: 5, display: "flex", flexDirection: "column", padding: 5, }}>
                { error && <Alert severity="error">{error}</Alert> }
                <TextField label="Email" margin="dense" type="email" onChange={(e) => {
                    setEmail(e.target.value);
                }}/>

                <TextField label="Password" margin="dense" type="password" onChange={(e) => {
                    setPassword(e.target.value);
                }}/>

                <Button onClick={login} variant="contained" sx={{ marginTop: 2, }}>Login</Button>

                <div>
                    Don't have an account? <Link to="/signup" style={{ color: '#000' }}>Create one here</Link>
                </div>
                <div>
                    <Link to="/request-password-reset" style={{ color: '#000' }}>Forgot your password?</Link>
                </div>
            </Container>
        </>
    )
}

export default Login;

Run Code Online (Sandbox Code Playgroud)

firebase.js

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';

const firebaseConfig = {
    apiKey,
    authDomain,
    projectId,
    storageBucket,
    messagingSenderId,
    appId,
    measurementId
}

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);

export {
    auth
};
Run Code Online (Sandbox Code Playgroud)

应用程序.js

import './App.css';
import Home from './components/Pages/Home';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Signup from './components/Pages/Signup/Signup';
import Login from './components/Pages/Login/Login';
import UserDashboard from './components/Pages/UserDashboard/UserDashboard';
import ForgotPassword from './components/Pages/Forgot-Password';

function App() {
  return (
      <Router>
          <div>
            <Switch>
              <Route exact path="/" component={Home}/>
              <Route path="/signup" component={Signup}/>
              <Route path="/login" component={Login}/>
              <Route path="/dashboard" component={UserDashboard}/>
              <Route path="/request-password-reset" component={ForgotPassword}/>
            </Switch>
          </div>
      </Router>
  );
}

export default App;

Run Code Online (Sandbox Code Playgroud)

Dre*_*ese 5

如果您尝试创建私有路由组件,而不将身份验证状态保留在应用程序中的某个位置并通过 React 上下文公开,那么您将需要在每次路由更改时异步检查身份验证状态。这意味着在进行身份验证状态检查时,您还需要“正在加载”或“待处理”状态。

下面是一个自定义私有路由的示例实现,没有任何持久状态。

import { useEffect, useState } from 'react';
import { Route, Redirect } from 'react-router-dom'; // v4/5
import { onAuthStateChanged } from 'firebase/auth';
import { auth }from '../../../firebase';

const PrivateRoute = props => {
  const [pending, setPending] = useState(true);
  const [currentUser, setCurrentUser] = useState();

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(
      auth,
      user => {
        setCurrentUser(user);
        setPending(false);
      },
      error => {
        // any error logging, etc...
        setPending(false);
      }
    );

    return unsubscribe; // <-- clean up subscription
  }, []);

  if (pending) return null; // don't do anything yet

  return currentUser 
    ? <Route {...props} />      // <-- render route and component
    : <Redirect to="/login" />; // <-- redirect to log in
};
Run Code Online (Sandbox Code Playgroud)

react-router-dom@6

自定义路由组件已在 v6 中推出,请使用布局路由。该PrivateRoute组件将替换RouteOutlet嵌套路由,以将其匹配的elementprop 渲染到其中,并Navigate替换Redirect.

import { useEffect, useState } from 'react';
import { Outlet, Navigate } from 'react-router-dom'; // v4/5
import { onAuthStateChanged } from 'firebase/auth';
import { auth }from '../../../firebase';

const PrivateRoute = props => {
  const [pending, setPending] = useState(true);
  const [currentUser, setCurrentUser] = useState();

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(
      auth,
      user => {
        setCurrentUser(user);
        setPending(false);
      },
      error => {
        // any error logging, etc...
        setPending(false);
      }
    );

    return unsubscribe; // <-- clean up subscription
  }, []);

  if (pending) return null; // don't do anything yet

  return currentUser 
    ? <Outlet />                        // <-- render outlet for routes
    : <Navigate to="/login" replace />; // <-- redirect to log in
};
Run Code Online (Sandbox Code Playgroud)

包裹您想要保护的路线。

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/signup" element={<Signup />} />
        <Route path="/login" element={<Login />} />
        <Route path="/request-password-reset" element={<ForgotPassword />} />
        <Route element={<PrivateRoute />}>
          <Route path="/dashboard" element={<UserDashboard />} />
        </Route>
      </Routes>
    </Router>
  );
}
Run Code Online (Sandbox Code Playgroud)