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?
Advertisement
Answer
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}/> }