基本的な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 }
問題:正解です。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;