Проблема с защищенными маршрутами, контекстным 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;