web-dev-qa-db-ja.com

保護されたルート、コンテキストAPI、Firebaseユーザー認証リクエストの問題

基本的なCRUD React認証にFirebaseを使用するアプリを作成しています。現在、Dashboardという名前のコンポーネントの保護されたルートを作成しようとしています。保護されたルートユーザーが認証されない限り、カプセル化されたルート(ダッシュボードなど)がレンダリングされないようにします。ユーザーが認証されていない場合、ルーターはランディングページにリダイレクトします。

私がこれを達成している方法は、 this の記事をモデルにしています:

上記の記事のパターンをエミュレートしましたが、正常に機能します。 Firebase(具体的にはFirebase認証)を組み込むと、ユーザーがログインしていてもアプリがDashboardコンポーネントをレンダリングしません。代わりに、ランディングページにリダイレクトするだけです。

私は問題が何であるかを知っています(私は思います)が、それを修正する方法がわかりません。

問題は、firebaseの呼び出しが非同期操作であり、firebaseの呼び出しが解決される前にダッシュボードが読み込まれようとすることです。

これを修正するためにコードを微調整できるかどうかを知りたいです。

FirebaseにAPI呼び出しを行うことができます毎回ユーザーが保護されたルートをロードします(認証を確認するため)が、コンテキストの状態に認証を設定し、それを参照したいと思いますユーザーがログインまたはログアウトするまで状態を示します。

以下に関連するコードを配置しました。すべてのファイルは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 }


17
William

問題:正解です。getUserへのAPI呼び出しは非同期であり、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;
12
Shubham Khatri