Проблема с защищенными маршрутами, контекстным API и запросом аутентификации пользователя firebase

Я пишу базовое приложение CRUD React, которое использует Firebase для аутентификации. В данный момент я пытаюсь создать защищенный маршрут для компонента с именем Dashboard. Защищенный маршрут гарантирует, что любые инкапсулированные маршруты (такие как Dashboard) не будут отображаться, пока пользователь не аутентифицирован. Если пользователь не аутентифицирован, маршрутизатор перенаправляет на целевую страницу.

То, как я это делаю, смоделировано на этой статье:

Я подражал шаблону в статье выше, и он отлично работает. Когда я включаю 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;




услуги/аутентификации

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 }


Ответ 1

Проблема: Вы действительно правы, 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;