- When the app is connected to Firestore and a user is signed in (in our app), an auth token infinite loop appears.
- Without Firestore connection, this effect doesn't occur.
- You can see the issue on the GIF file included herein as the link (see below).
- Interestingly, this problem is present only when the project runs on Linux Mint (19.1 Cinnamon) in contrast to Windows 10 or 8.1 (I didn't run it on Mac).
- The project was bootstrapped with Create React App.
Video-presentation of the issue
I integrated Firebase with ReactJS with this tutorial: https://www.robinwieruch.de/complete-firebase-authentication-react-tutorial
and found very useful the class-based Firebase connection and HOC/Context API session handling presented therein. However, I have no idea how to fix this auth token infinite loop.
Below, there are files responsible for signing in/out and Firebase connection, as well as package.json:
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';
import App from './App';
import Firebase, { FirebaseContext } from './components/Firebase';
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
App.js:
import React from 'react';
import {
BrowserRouter as Router,
Route,
Switch
} from 'react-router-dom';
import Navigation from './components/Navigation';
import LandingPage from './components/LandingPage';
import SignInPage from './components/SignInPage';
import HomePage from './components/HomePage';
import HomePage2 from './components/HomePage2';
import { withAuthentication } from './components/Session';
import * as ROUTES from './components/constants/routes';
const App = () => (
<Router>
<Navigation />
<hr />
<Switch>
<Route exact path={ROUTES.LANDING} component={LandingPage} />
<Route path={ROUTES.SIGN_IN} component={SignInPage} />
<Route path={ROUTES.HOME} component={HomePage} />
<Route path={ROUTES.HOME2} component={HomePage2} />
</Switch>
</Router>
);
export default withAuthentication(App);
Firebase / context.js:
import React, { createContext } from 'react';
const FirebaseContext = createContext(null);
export const withFirebase = Component => props => (
<FirebaseContext.Consumer>
{firebase => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
);
export default FirebaseContext;
Firebase / firebase.js:
import app from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
const config = {
apiKey: process.env.REACT_APP_API_KEY,
authDomain: process.env.REACT_APP_AUTH_DOMAIN,
databaseURL: process.env.REACT_APP_DATABASE_URL,
projectId: process.env.REACT_APP_PROJECT_ID,
storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
appId: process.env.REACT_APP_ID
};
class Firebase {
constructor() {
app.initializeApp(config);
this.auth = app.auth();
this.db = app.firestore();
}
// Auth API
doSignInWithEmailAndPassword = (email, password) => this.auth.signInWithEmailAndPassword(email, password);
doSignOut = () => this.auth.signOut();
}
export default Firebase;
Firebase / index.js:
import FirebaseContext, { withFirebase } from './context';
import Firebase from './firebase';
export default Firebase;
export { FirebaseContext, withFirebase };
Session / context.js:
import { createContext } from 'react';
const AuthUserContext = createContext(null);
export default AuthUserContext;
Session / index.js:
import AuthUserContext from './context';
import withAuthentication from './withAuthentication';
import withAuthorization from './withAuthorization';
export { AuthUserContext, withAuthentication, withAuthorization };
Session / withAuthentication.js:
import React, { useState, useEffect } from 'react';
import AuthUserContext from './context';
import { withFirebase } from '../Firebase';
const withAuthentication = Component => {
const WithAuthentication = (props) => {
const [authUser, setAuthUser] = useState(null);
useEffect(() => {
const listener = props.firebase.auth.onAuthStateChanged(authUser => {
authUser
? setAuthUser(authUser)
: setAuthUser(null);
});
return () => listener();
}, []);
return (
<AuthUserContext.Provider value={authUser}>
<Component {...props} />
</AuthUserContext.Provider>
);
};
return withFirebase(WithAuthentication);
};
export default withAuthentication;
Session / withAuthorization.js:
import React, { useEffect } from 'react';
import AuthUserContext from './context';
import { withFirebase } from '../Firebase';
import * as ROUTES from '../constants/routes';
const withAuthorization = (Component) => {
const WithAuthorization = (props) => {
useEffect(() => {
const listener = props.firebase.auth.onAuthStateChanged(
authUser => {
if (!authUser) {
props.history.push(ROUTES.SIGN_IN);
}
}
);
return () => listener();
}, []);
return (
<AuthUserContext.Consumer>
{authUser => !!authUser && <Component {...props} />}
</AuthUserContext.Consumer>
);
};
return withFirebase(WithAuthorization);
};
export default withAuthorization;
package.json:
{
"name": "react-firebase-test",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"firebase": "^7.14.5",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"recompose": "^0.30.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"dotenv": "^8.2.0"
}
}