I'm trying to create protected routes that are only viable while user is logged in, but I have trouble getting loggedIn state in ProtectedRoutes component, it's always set to false thus redirecting to "/login". What am I not getting correctly here?
App.tsx
interface loginContextInterface {
loggedIn: boolean;
setLoggedIn: (value: (((prevState: boolean) => boolean) | boolean)) => void;
}
export const LoginContext = createContext({} as loginContextInterface);
export default function App() {
const [ loggedIn, setLoggedIn ] = useState(false)
useEffect(() => {
console.log("before", loggedIn)
isLoggedIn().then((r) => {
console.log("R", r)
setLoggedIn(r)
})
console.log("after", loggedIn)
}, [loggedIn])
return (
<LoginContext.Provider value={{loggedIn, setLoggedIn}}>
<Router>
<MenuHeader />
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/tasks" element={<ProtectedRoutes/>}>
<Route path="/" element={<Tasks/>}/>
</Route>
<Route path="/login" element={<Login />}/>
<Route path="/logout" element={<Logout />}/>
<Route path="/register" element={<Register/>}/>
</Routes>
</Router>
</LoginContext.Provider>
);
}
ProtectedRoutes.tsx
export const ProtectedRoutes = () =>{
const location = useLocation();
const {loggedIn} = useContext(LoginContext)
console.log("protected", loggedIn)
return (
loggedIn ? <Outlet/> : <Navigate to={"/login"} replace state={{location}}/>
);
}
Edit:
isLoggedIn just authenticates that the user is logged in via cookie using api on the server side. Added logging
Produces these after trying to access /tasks route and redirecting me to /login again
VM109:236 protected false
App.tsx:21 before false
App.tsx:26 after false
App.tsx:21 before false
App.tsx:26 after false
2App.tsx:23 R true
App.tsx:21 before true
App.tsx:26 after true
App.tsx:23 R true
CodePudding user response:
It's not recommended to reset the dependency inside the useEffect(), it may cause an infinite loop.
useEffect(() => {
isLoggedIn().then((r) => setLoggedIn(r)) // loggedIn will be update here and trigger
the useEffect agan
}, [loggedIn])
What does the console.log(loggedIn) and console.log(r)
show? I'm guessing isLoggedIn returns false, loggedIn is set to false initially so useEffect not being triggered again and it remains as false
CodePudding user response:
There is an issue with the useEffect
hook, using the loggedIn
state value as the dependency. You should not use dependencies that are unconditionally updated by the hook callback. My guess here is that you wanted to do an initial authentication check when the app mounts. You can remove loggedIn
from the dependency since it's not referenced at all.
useEffect(() => {
isLoggedIn().then(setLoggedIn);
}, []);
I suggest also using an initial loggedIn
state value that doesn't match either the authenticated or unauthenticated states, i.e. something other than true|false
. This is so the ProtectedRoutes
can conditionally render null or some loading indicator while any pending authentication checks are in-flight and there isn't any confirmed authentication state already saved in state.
Update the context to declare loggedIn
optional.
interface loginContextInterface {
loggedIn?: boolean;
setLoggedIn: React.Dispatch<boolean>;
}
Update App
to have an initially undefined loggedIn
state value.
const [loggedIn, setLoggedIn] = useState<boolean | undefined>();
Update ProtectedRoutes
to check for the undefined loggedIn
state to render a loading indicator and not immediately bounce the user to the login route.
const ProtectedRoutes = () => {
const location = useLocation();
const { loggedIn } = useContext(LoginContext);
if (loggedIn === undefined) {
return <>Checking authentication...</>; // example
}
return loggedIn ? (
<Outlet />
) : (
<Navigate to={"/login"} replace state={{ location }} />
);
};
Also in App
, remove/move the "/tasks"
path from the layout route rendering the ProtectedRoutes
component to the nested route rendering the Tasks
component. The reason is that it's invalid to nest the absolute path "/"
in "/tasks"
.
<Route element={<ProtectedRoutes />}>
<Route path="/tasks" element={<Tasks />} />
</Route>