Reputation: 2552
I have currently initialized a React App with firebase. Within the application, I have created an open login route and a private Home route using react-router-dom. my app.js looks like so:
App.js:
import React from 'react'
import { BrowserRouter, Switch, Route } from 'react-router-dom'
import Login from './pages/Login'
import Home from './pages/Home'
import PrivateRoute from './utils/PrivateRoute'
const App = () => {
return (
<BrowserRouter>
<Switch>
<PrivateRoute exact path='/' component={Home} />
<Route path='/login' component={Login} />
</Switch>
</BrowserRouter>
)
}
export default App
I am storing the currentUser in context using the onAuthStateChanged firebase event listener like so:
AppContext:
import { useEffect, useState, createContext } from 'react'
import { auth } from '../utils/firebase'
export const AppContext = createContext()
export const AppProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null)
useEffect(() => {
auth.onAuthStateChanged(setCurrentUser)
}, [])
return (
<AppContext.Provider value={{ currentUser }}>
{children}
</AppContext.Provider>
)
}
when a user logins in via the login route:
login:
import React, { useState, useCallback, useContext } from 'react'
import { auth } from '../utils/firebase'
import { useHistory, Redirect } from 'react-router-dom'
function Login() {
const [formData, setFormData] = useState({ email: '', password: '' })
const history = useHistory()
const handleChange = ({ target: { name, value } }) => {
setFormData({ ...formData, [name]: value })
}
const handleSubmit = useCallback(
async event => {
event.preventDefault()
await auth
.createUserWithEmailAndPassword(formData.email, formData.password)
.then(user => {
console.log(user)
history.push('/')
})
.catch(err => {
alert(err)
})
},
[history, formData.email, formData.password]
)
return (
<div className='form-container sign-up-container'>
<form className='register-form' onSubmit={handleSubmit}>
<h1>Create Account</h1>
<div className='social-container'>
<div className='social'>
<i className='fab fa-facebook-f'></i>
</div>
<div className='social'>
<i className='fab fa-google-plus-g'></i>
</div>
<div className='social'>
<i className='fab fa-linkedin-in'></i>
</div>
</div>
<span>or use your email for registration</span>
<input
type='email'
placeholder='Email'
name='email'
onChange={handleChange}
/>
<input
type='password'
placeholder='Password'
name='password'
onChange={handleChange}
/>
<button type='submit'>Sign Up</button>
</form>
</div>
)
}
export default Login
the currentUser is successfully stored in context and the user is pushed into the private Home route.
the Private Route looks like so:
import React, { useContext } from 'react'
import { Route, Redirect } from 'react-router-dom'
import { AppContext } from '../context/AppContext'
const PrivateRoute = ({ component: Component, ...rest }) => {
const { currentUser } = useContext(AppContext)
return (
<Route
{...rest}
render={routeProps =>
!!currentUser ? (
<Component {...routeProps} />
) : (
<Redirect to={'/login'} />
)
}
/>
)
}
export default PrivateRoute
The issue I'm having is that when the app refreshes, the currentUser becomes null initially and then currentUser's information loads back up. While the currentUser is null on refresh, the user is kicked from the home route and redirected to the login page. I'm wondering if anyone has any suggestions on how to prevent this from happening.
Upvotes: 0
Views: 937
Reputation: 317352
You initialize your currentUser
state variable with a null value:
const [currentUser, setCurrentUser] = useState(null)
This means that currentUser
is always initially null when the page is first loaded. The prior user object isn't known for sure until some time later, after it's asynchronously loaded by the Firebase SDK. Your code needs to be ready for this. If you require that a user be signed in before rendering your component, you should wait for the first time onAuthStateChanged
triggers with an actual user object.
You can read more about this behavior of the Firebase Auth SDK in this blog.
Upvotes: 1