I am writing a basic CRUD React app that uses Firebase for authentication. At the moment I am trying to create a protected route for a component named Dashboard. The protected route insures that any encapsulated routes (such as the Dashboard) do not render unless the user is authenticated.If the user is not authenticated then the router redirects to the landing page.
The way that I am accomplishing this is modelled on this article:
I have emulated the pattern in the article above and it works fine. When I incorporate firebase (specifically firebase authentication) my app does not render the Dashboard component even when a user has logged in. Instead it simply redirects to the landning page
I know what the problem is (I think) but I am unsure how to fix it.
The problem is that the call to firebase is an async operation and the dashboard attempts to load before the call to firebase is resolved.
I want to know if their are any tweaks to my code I can make to fix this.
I could make an api call to firebase every time the user loads a protected route (to check for authentication) but I would prefer to set the authentication on the state of the Context and reference that state until the user either logs in or logs out.
I've placed the relevant code below. All files are in the src
directory
Thank you!
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 }
Problem: You are indeed correct, the API call to getUser
is async and is trigged in componentDidMount
, hence by the time authentication
state is set to true
, the Redirect
component has already been triggered.
Solution: What you need to wait till the authentication request is a success and then take a decision to load the Route or redirect in ProtectedRoute
component.
In order to make it work, you need a loading state.
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With