For a small project of mine, I'm trying to implement the most basic authentication as possible, using the React context API without Redux.
import { createContext, useContext, useState } from 'react'
export const AuthContext = createContext()
export const useAuth = () => {
const context = useContext(AuthContext)
if(context === null) throw new Error('Nope')
return context
}
export const AuthProvider = (props) => {
const [authenticated, setAuthenticated] = useState(false)
const login = () => {
setAuthenticated(true)
localStorage.setItem(storageKey, true)
}
const logout = () => {
setAuthenticated(false)
localStorage.setItem(storageKey, false)
}
return <AuthContext.Provider value={{authenticated, login, logout}} {...props}/>
}
export default AuthContext
I created a context, and wrapped my <App />
component in it like so; <AuthProvider></App></AuthProvider>
. Because I want to keep the authenticated state, I used the browser's local storage, for storing a simple boolean value.
import PrivateRoute from './PrivateRoute'
import { useAuth } from './context/AuthContext'
import { AuthPage } from './pages'
import {
BrowserRouter,
Switch,
Route,
} from 'react-router-dom'
import { useEffect } from 'react'
const App = () => {
const { login, authenticated } = useAuth()
useEffect(() => {
const token = localStorage.getItem('user')
if(token && token !== false) { login() }
})
return (
<BrowserRouter>
<Switch>
<PrivateRoute exact path="/auth" component={AuthPage} />
<Route exact path='/'>
Dashboard
</Route>
</Switch>
</BrowserRouter>
)
}
export default App
Then, in my <App />
component, I tried invoking the login
callback, given from the AuthProvider
, which made me assume that made me login during page refreshes. When I try to access the authenticated
variable in the current component, it does work. It shows that I am authenticated.
However when I try to set up a PrivateRoute, which only authenticated users can go to like this:
import {
Route,
Redirect
} from 'react-router-dom'
import { useAuth } from './context/AuthContext'
const PrivateRoute = ({ component: Component, ...rest }) => {
const { authenticated } = useAuth()
if(authenticated) {
return <Route {...rest} render={(props) => <Component {...props} />} />
}
return <Redirect to={{ pathname: '/login' }} />
}
export default PrivateRoute
It does not work. It just redirects me to the login page. How does this come? The PrivateRoute
component is getting rendered from the <App />
component. Also, what would be the solution to this problem?
Rather than running a useEffect
on every rerender to check if user should be logged in, you should better initialize your authenticated
state with the values from your localStorage:
const storageKey = 'user'
const initialState = JSON.parse(localStorage.getItem(storageKey)) ?? false
export const AuthProvider = (props) => {
const [authenticated, setAuthenticated] = useState(initialState)
const login = () => {
setAuthenticated(true)
localStorage.setItem(storageKey, true)
}
const logout = () => {
setAuthenticated(false)
localStorage.setItem(storageKey, false)
}
return <AuthContext.Provider value={{authenticated, login, logout}} {...props}/>
}