受保护路由、上下文 API 和 firebase 用户身份验证请求的问题

Problem with protected routes, context API and firebase user authentication request

我正在编写一个使用 Firebase 进行身份验证的基本 CRUD React 应用程序。目前我正在尝试为名为 Dashboard 的组件创建一个受保护的路由。受保护的路由确保任何封装的路由(例如仪表板)不会呈现,除非用户 authenticated.If 用户未通过身份验证然后路由器重定向到登录页面。

我完成此操作的方式是仿照 this 文章:

我模拟了上面文章中的模式,效果很好。当我合并 firebase(特别是 firebase 身份验证)时,即使用户已登录,我的应用程序也不会呈现 Dashboard 组件。相反,它只是重定向到着陆页

我知道问题出在哪里(我认为),但我不确定如何解决它。

问题是对 firebase 的调用是一个异步操作,仪表板会在对 firebase 的调用被解析之前尝试加载。

我想知道他们是否可以对我的代码进行任何调整以解决此问题。

我可以 api 调用 firebase 每次 用户加载受保护的路由(以检查身份验证),但我更愿意将身份验证设置为上下文的状态并引用该状态,直到用户登录或注销。

我已将相关代码放在下面。所有文件都在 src 目录

谢谢!

App.js

import React, { Component } from 'react';
import { BrowserRouter, Route, Redirect } from "react-router-dom";
import {Switch} from 'react-router';
import Landing from './PageComponents/Landing';
import {Provider} from './PageComponents/Context';
import Dashboard from './PageComponents/Dashboard';
import ProtectedRoute from './PageComponents/ProtectedRoute';

class App extends Component {
  render() {
    return (
      <div className="App">

      <Provider>
        <BrowserRouter>

        <div>
          <Switch>

            <Route exact={true} path="/" component={Landing} />
            <ProtectedRoute exact path="/dashboard" component={Dashboard} /> 

          </Switch>
        </div>

        </BrowserRouter>
        </Provider>

      </div>
    );
  }
}

export default App;


PageComponents/Context.js

import React from 'react';
import { getUser } from '../services/authentication';

let Context = React.createContext();

class Provider extends React.Component {

    state = {
        userID: true,
        user:undefined,
        authenticated:false
    }

    async getUser(){

        try{

            let user = await getUser();

            return user

        } catch(error){

            console.log(error.message)
        }

    }


    async componentDidMount(){

        console.log("waiting to get user")
        let user = await this.getUser();
        console.log(user)
        console.log("got user")

        this.setState({
          userID: user.uid,
          user:user,
          authenticated:true
        })
    }


    render(){
        console.log(this.state)
        return(
            <Context.Provider value={{   
                 state:this.state
            }}>
                {this.props.children}
            </Context.Provider>
        )
    }
}

const Consumer = Context.Consumer;

export {Provider, Consumer};

PageComponents/Dashboard

import * as React from 'react';
import {Consumer} from '../../PageComponents/Context';



class Dashboard extends React.Component {
  render(){

    console.log("Dashboard component loading....")

     return(


        <Consumer>
            {(state)=>{
                console.log(state)
                return(
                  <div>
                    <p> Dashboard render</p>
                  </div>
                )
             }}
        </Consumer>
     )
  }
}

export default Dashboard

PageComponents/ProtectedRoute


import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { Consumer } from '../PageComponents/Context';


const ProtectedRoute = ({ component: Component, ...rest }) => {
    console.log("Middleware worked!");

    return (

        <Consumer>
               {(context)=>{
                /*________________________BEGIN making sure middleware works and state is referenced */
                  console.log(context);
                  console.log("Middle ware");
                /*________________________END making sure middleware works and state is referenced */

                 console.log(  context.getState().authenticated + " <--- is authenticated from ProtectedRoute. true or false?")

                  if(context.state.authenticated){

                    return(

                        <Route {...rest} render={renderProps => {
                           console.log(renderProps);
                           return (<Component {...renderProps} />)
                        }}/>

                    )  

                  }else{

                    return <Redirect to="/"/>

                  }

                }}

        </Consumer>
    )


};

export default ProtectedRoute;




services/authentication

import firebase from '../../services/firebase'




const getUser = () =>{

    return new Promise((resolve, reject) => {   // Step 3. Return a promise

         //___________________ wrapped async function

         firebase.auth().onAuthStateChanged((user)=> {

                if(user){

                    resolve(user);   //____This is the returned value of a promise

                 }else{

                   reject(new Error("Get user error"))

                }
         })

       //_____________________END wrapped async function  

    });

}


export {getUser }


问题:你确实是对的,API对getUser的调用是异步的并且在componentDidMount中触发,因此由authentication 状态设置为 true 时,Redirect 组件已被触发。

解决方案: 需要等到认证请求成功,然后决定加载路由或在ProtectedRoute组件中重定向。

为了让它工作,你需要一个加载状态。


PageComponents/Context.js

let Context = React.createContext();

class Provider extends React.Component {

    state = {
        userID: true,
        user:undefined,
        loading: true,
        authenticated:false
    }

    async getUser(){
       let user = await getUser();
       return user

    }


    async componentDidMount(){
        console.log("waiting to get user")
        try {
          let user = await this.getUser();
          console.log(user)
          console.log("got user")

          this.setState({
            userID: user.uid,
            user:user,
            loading: false,
            authenticated:true
          })
       } catch(error) {
           console.log(error);
           this.setState({
               loading: false,
               authenticated: false
           })
       }
    }


    render(){
        return(

            <Context.Provider value={{
                 state:this.state
            }}>
              {this.props.children}
            </Context.Provider>

        )
    }
}


const Consumer = Context.Consumer;

export {Provider, Consumer}

PageComponents/ProtectedRoute

import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { Consumer } from '../PageComponents/Context';


const ProtectedRoute = ({ component: Component, ...rest }) => {
    console.log("Middleware worked!");
    return (
        <Consumer>
            {(context)=>{
                /* BEGIN making sure middleware works and state is referenced */
                  console.log(context);
                  console.log("Middle ware");
                /* END making sure middleware works and state is referenced */

                 console.log(  context.getState().authenticated + " <--- is authenticated from ProtectedRoute. true or false?")

                 // Show loading state 
                 if (context.state.loading) {
                     return <Loader /> 
                 }
                 if(context.state.authenticated){
                    return(
                        <Route {...rest} render={renderProps => {
                           console.log(renderProps);
                           return (<Component {...renderProps} />)
                        }}/>
                    )  
                  }else{
                    return <Redirect to="/"/>
                  }
                }}
        </Consumer>
    )
};

export default ProtectedRoute;