首页 > 解决方案 > React 中的路由器、重定向和身份验证上下文问题

问题描述

我对 React 钩子还是新手,我正在尝试调整一些代码,以便仅在用户在 Firebase 中进行身份验证时才允许访问某些路由。同时,如果用户键入不存在的路径,我希望用户被重定向到家。如果他在注销时试图访问受保护的路线之一,他也应该被重定向到家。这是我的 App.js 的样子:

import React, {Fragment, lazy, Suspense, useState, useEffect} from "react";
import {CssBaseline, MuiThemeProvider} from "@material-ui/core";
import {BrowserRouter, Redirect, Route, Switch} from "react-router-dom";
import theme from "./theme";
import GlobalStyles from "./GlobalStyles";
import * as serviceWorker from "./serviceWorker";
import Pace from "./shared/components/Pace";
import Firebase from "./firebase";
import FirebaseContext from "./firebase-context";

const firebase = new Firebase();

const LoggedInComponent = lazy(() => import("./logged_in/components/Main"));

const LoggedOutComponent = lazy(() => import("./logged_out/components/Main"));

function onAuthStateChange(callback) {
    return firebase.auth.onAuthStateChanged(user => {
        if(user) {
            callback({loggedIn: true});
        } else {
            callback({loggedIn: false});
        }
    });
}

function App() {
    const [user, setUser] = useState({loggedIn: false});

    useEffect(() => {
        const unsubscribe = onAuthStateChange(setUser);
        return () => {
            unsubscribe();
        }
    }, [setUser]);
    console.log("logged in?", user.loggedIn);

    return (
        <BrowserRouter>
            <FirebaseContext.Provider value={firebase}>
                <MuiThemeProvider theme={theme}>
                    <CssBaseline/>
                    <GlobalStyles/>
                    <Pace color={theme.palette.primary.light}/>
                    <Suspense fallback={<Fragment/>}>
                        <Switch>
                            {user.loggedIn && <Route path="/c" component={LoggedInComponent}/>}
                            <Route exact path="/" component={LoggedOutComponent}/>
                            <Redirect to="/"/>
                        </Switch>
                    </Suspense>
                </MuiThemeProvider>
            </FirebaseContext.Provider>
        </BrowserRouter>
    );
}

serviceWorker.register();

export default App;

我遇到的问题是,当我登录并且我在主页上时。如果我尝试将“/c”添加到 URL 以访问受保护的部分,我会被重定向回主页,就好像我没有登录一样。

当我登录时重新加载主页时,我得到 2 个控制台日志,一个说我没有登录,一个说我已经登录。而且我认为正在发生的事情是,在应用程序有机会呈现并看到我已登录之前,我被重定向了。这似乎得到了这样一个事实的证实,即如果我注释掉我的重定向,我可以到达/c. 但是我没有得到该重定向的其他好处。

我难以理解的是为什么user.loggedIn第一次渲染组件时是错误的。

标签: reactjsfirebasefirebase-authenticationreact-routerreact-hooks

解决方案


通过使用这个第三方库,我想出了如何在不重新发明轮子的情况下(并且不使用依赖于疯狂的实验性 React 东西的 ReactFire)来做到这一点。这是生成的代码:

import React, { Fragment, Suspense, lazy, useContext } from "react";
import { MuiThemeProvider, CssBaseline } from "@material-ui/core";
import { Route, Switch, Redirect } from "react-router-dom";
import theme from "./theme";
import GlobalStyles from "./GlobalStyles";
import * as serviceWorker from "./serviceWorker";
import Pace from "./shared/components/Pace";
import {useAuthState} from "react-firebase-hooks/auth";
import FirebaseContext from "./firebase-context";
import LinearProgress from "@material-ui/core/LinearProgress";

const LoggedInComponent = lazy(() => import("./logged_in/components/Main"));

const LoggedOutComponent = lazy(() => import("./logged_out/components/Main"));

function App() {
  const firebase = useContext(FirebaseContext);
  const [user, initialising, error] = useAuthState(firebase.auth);
  console.log("user", user);
  console.log("initialising", initialising);
  console.log("error", error);
  return (
      <MuiThemeProvider theme={theme}>
        <CssBaseline />
        <GlobalStyles />
        <Pace color={theme.palette.primary.light} />
        {initialising ? <LinearProgress/> :
        <Suspense fallback={<Fragment />}>
          <Switch>
            {user && <Route path="/c">
              <LoggedInComponent />
            </Route>}
            <Route exact path="/">
              <LoggedOutComponent />
            </Route>
            <Redirect to="/"/>
          </Switch>
        </Suspense>}
      </MuiThemeProvider>
  );
}

serviceWorker.register();

export default App;

事实上,我必须延迟路由开关的显示,直到 Firebase auth 的钩子完成初始化。


推荐阅读