'State will not stay updated to keep a user logged in, even though the token remains in localStorage
I'm creating a basic app using React and Express. The issue I'm having is that I can login a user and register a new user, but when I hit the refresh button, it logs the same user out, immediately. In addition, their authorization token is still present in the localStorage even though the user is logged out. I don't think its coming from the server because the server (via Postman) performs all necessary functions as needed. The error has to be coming from the front end. In addition, I had my actions in state set within a try/catch block. I removed the catch for errors block because whenever I would register/login a user, it would perform the behaviors as needed, but the authorization would come back as a 401. So in short, I could register a user, but I couldn't login a user, nor keep that new user logged in. Once I removed the try/catch block and just created a basic async/await function, it would allow me to register/login a user without any issues....except now, I can't stay logged in after refreshing the page. Here is my code.
Register.js
...imports
const Register = () => {
const alertContext = useContext(AlertContext)
const authContext = useContext(AuthContext)
const { setAlert } = alertContext
const { register, error, clearErrors, isAuthenticated, loggedIn, loadUser } = authContext
const [user, setUser] = useState({
name: '',
email: '',
password: '',
password2: '',
})
const [ activeUser, setActiveUser ] = useState()
const { name, email, password, password2 } = user
useEffect(() => {
const loggedInUser = localStorage.getItem('token')
if(loggedIn === true && loggedInUser){
loadUser()
setActiveUser(loggedInUser)
}
if(error === 'User already exists'){
setAlert(error, 'danger')
clearErrors()
}
// eslint-disable-next-line
}, [error])
const onChange = (e) => {
setUser({ ...user, [e.target.name]: e.target.value })
}
const onSubmit = (e) => {
e.preventDefault()
if (name === '' || email === '' || password === '') {
setAlert('Please enter all fields', 'danger')
} else if (password !== password2) {
setAlert('Passwords do not match', 'danger')
} else {
register({
name,
email,
password
})
}
}
if (isAuthenticated) return <Navigate to='/' />
return (
<div className='form-container'>
<h1>
Account <span className='text-primary'>Register</span>
</h1>
<form onSubmit={onSubmit}>
<div className='form-group'>
<label htmlFor='name'>Name</label>
<input type='text' name='name' value={name} onChange={onChange} />
<label htmlFor='email'>Email</label>
<input type='email' name='email' value={email} onChange={onChange} />
<label htmlFor='password'>Password</label>
<input
type='password'
name='password'
value={password}
onChange={onChange}
minLength='6'
/>
<label htmlFor='password2'>Confirm Password</label>
<input
type='password'
name='password2'
value={password2}
onChange={onChange}
minLength='6'
/>
</div>
<button
type='submit'
className='btn btn-primary btn-block'
> Register
</button>
</form>
</div>
)
}
export default Register
AuthState.js
imports..
const AuthState = ({ children }) => {
const intitialState = {
token: localStorage.getItem("token"),
isAuthenticated: null,
loading: true,
error: null,
user: null,
loggedIn: false
}
const [state, dispatch] = useReducer(AuthReducer, intitialState)
//Load User
const loadUser = async () => {
const { token } = state
const config = {
headers: {
'Content-type': 'application/json',
'x-auth-token': token
}
}
const res = await axios.get('/api/auth', config)
dispatch({
type: USER_LOADED,
payload: res.data
})
}
//Login User
const login = async (formData) => {
const config = {
headers: {
'Content-type': 'application/json'
}
}
const res = await axios.post('/api/auth', formData, config)
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
})
loadUser()
}
//Register User
const register = async (formData) => {
const config = {
headers: {
'Content-type': 'application/json',
}
}
const res = await axios.post('/api/users', formData, config)
dispatch({
type: REGISTER_SUCCESS,
payload: res.data
})
}
//Logout
const logout = () => {
localStorage.removeItem('token')
dispatch({
type: LOGOUT
})
}
//Clear Errors
const clearErrors = () => {
dispatch({ type: CLEAR_ERRORS})
}
const { token, isAuthenticated, loading, error, user, loggedIn } = state
return (
<authContext.Provider
value={{
token,
isAuthenticated,
loading,
error,
user,
loggedIn,
register,
clearErrors,
loadUser,
login,
logout
}}
>
{children}
</authContext.Provider>
)
}
export default AuthState
PrivateRoute.js
const PrivateRoute = ({ component: Component }) => {
const authContext = useContext(AuthContext)
const { isAuthenticated, loggedIn } = authContext
if (isAuthenticated || loggedIn) return <Component />;
return <Navigate to='/login' />;
}
Navbar.js (for logging out)
const Navbar = ({ title, icon }) => {
const authContext = useContext(AuthContext)
const { isAuthenticated, logout, user } = authContext
const onLogout = () => {
logout()
}
const authLinks = (
<>
<li> Hello { user && user.user.name }</li>
<li>
<a onClick={onLogout} href="#!">
<i className="fas fa-sign-out-alt" /><span className="hide-sm">Logout</span>
</a>
</li>
</>
)
const guestLinks = (
<>
<li>
<Link to='/register'>Register</Link>
</li>
<li>
<Link to='/login'>Login</Link>
</li>
</>
)
return (
<div className="navbar bg-primary">
<h1>
<i className={icon} /> {title}
</h1>
<ul>
{ isAuthenticated ? authLinks : guestLinks }
</ul>
</div>
)
}
AuthReducer.js
imports..
export default (state, action) => {
switch(action.type) {
case REGISTER_SUCCESS:
localStorage.setItem('token', action.payload.token)
return {
...state,
...action.payload,
isAuthenticated: true,
loading: false,
loggedIn: true
}
//case REGISTER_FAIL:
//case AUTH_ERROR:
//case LOGIN_FAIL:
case LOGOUT:
return {
...state,
token: null,
isAuthenticated: false,
loading: false,
user: null,
error: action.payload,
loggedIn: false
}
case CLEAR_ERRORS:
return {
...state,
error: null
}
case USER_LOADED:
return {
...state,
isAuthenticated: true,
loading: false,
user: action.payload,
loggedIn: true
}
case LOGIN_SUCCESS:
// eslint-disable-next-line
case REGISTER_SUCCESS:
localStorage.setItem('token', action.payload.token)
return {
...state,
...action.payload,
isAuthenticated: true,
loading: false,
loggedIn: true
}
default:
throw Error(`Unhandled type: ${action.type}, ${action.payload}`)
}
}
App.js
...
import AuthState from './components/context/auth/AuthState'
import Register from './components/auth/Register'
import Login from './components/auth/Login'
import PrivateRoute from './components/routing/PrivateRoute'
const App = () => {
return (
<AuthState>
<ContactState>
<AlertState>
<Router>
<div className='App'>
<Navbar />
<div className='container'>
<Alerts />
<Routes>
< Route path='/' element={<PrivateRoute component={Home} />} />
<Route path='/about' element={<About />} />
<Route path='/register' element={<Register />} />
<Route path='/login' element={<Login />} />
</Routes>
</div>
</div>
</Router>
</AlertState>
</ContactState>
</AuthState>
)
}
export default App
here is when my state looks like before I register/login a user.
[
{
"name": "Reducer",
"value": {
"token": null,
"isAuthenticated": null,
"loading": true,
"error": null,
"user": null,
"loggedIn": false
},
"subHooks": [],
"hookSource": {
"lineNumber": 1715,
"functionName": "AuthState",
"fileName": "http://localhost:4445/static/js/bundle.js",
"columnNumber": 78
}
}
]
Here is my state after registering/logging in a user
[
{
"name": "Reducer",
"value": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjI4YmZjZGYyMTE1MDFhMmJmYjViYzU0In0sImlhdCI6MTY1MzM0MTQwOCwiZXhwIjoxNjUzNzAxNDA4fQ.9O6Loe_8gSEXJ5k0xZP-J4f5LtlDYKnSmwmC2HFJTrA",
"isAuthenticated": true,
"loading": false,
"error": null,
"user": "{user: {…}}",
"loggedIn": true
},
"subHooks": [],
"hookSource": {
"lineNumber": 1715,
"functionName": "AuthState",
"fileName": "http://localhost:4445/static/js/bundle.js",
"columnNumber": 78
}
}
]
Upon refreshing the page, this is state
[
{
"name": "Reducer",
"value": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjI4YmZjZGYyMTE1MDFhMmJmYjViYzU0In0sImlhdCI6MTY1MzM0MTQwOCwiZXhwIjoxNjUzNzAxNDA4fQ.9O6Loe_8gSEXJ5k0xZP-J4f5LtlDYKnSmwmC2HFJTrA",
"isAuthenticated": null,
"loading": true,
"error": null,
"user": null,
"loggedIn": false
},
"subHooks": [],
"hookSource": {
"lineNumber": 1715,
"functionName": "AuthState",
"fileName": "http://localhost:4445/static/js/bundle.js",
"columnNumber": 78
}
}
]
In my Applications tab, the token is still present in localStorage, but the user is logged out.
Key: Token
value: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjI4YmZjZGYyMTE1MDFhMmJmYjViYzU0In0sImlhdCI6MTY1MzM0MTQwOCwiZXhwIjoxNjUzNzAxNDA4fQ.9O6Loe_8gSEXJ5k0xZP-J4f5LtlDYKnSmwmC2HFJTrA
Any help would be greatly appreciated, I've been working on this for about 4 days and cannot understand exactly where I'm going wrong.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|