Any help or advice on this issue would be much appreciated!
I am creating an app with react router and TypeScript and I am using an AuthContext store my user data. I am having an issue when trying to protect some of my routes with a RequireAuth component because it seems that the RequireAuth component is called / rendered / mounted (I'm not sure what the appropriate term would be here) before the AuthContext. Therefore, my user data is always empty and I am stuck in a loop of logging in and being redirected to the login page.
I am sure that the issue is propably something to do with the order in which I mount my components but I am not sure what I'm missing.
Any hints or suggestions would be very welcome!
App.tsx
import { useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { AuthContextProvider } from './contexts/AuthContext';
import { useFuncContext } from './contexts/FuncContext';
import { PlaylistContextProvider } from './contexts/PlaylistContext';
import RequireAuth from './components/RequireAuth';
import LandingPage from './pages/LandingPage';
import HomePage from './pages/HomePage';
import Setup from './pages/Setup';
import NotFound from './pages/NotFound';
import Loader from './components/Loader';
import Popup from './components/Popup';
import ErrorAlert from './components/ErrorAlert';
function App() {
const func = useFuncContext();
useEffect(() => {
if (func?.error) {
console.log(func?.error);
const errorTimeout = setTimeout(() => func?.setError(''), 3000);
return () => clearTimeout(errorTimeout);
}
else if (func?.popupMessage) {
console.log(func?.popupMessage);
const popupTimeout = setTimeout(() => func?.setPopupMessage(''), 3000);
return () => clearTimeout(popupTimeout);
}
}, [func]);
return (
<>
<div className="App">
{func?.popupMessage && <Popup message={func?.popupMessage} />}
{func?.error && <ErrorAlert errorMessage={func?.error} />}
</div>
{func?.isLoading ? (
<Loader className='loading' />
) : (
<AuthContextProvider>
<PlaylistContextProvider>
<Router>
<Routes>
<Route path='/' element={<LandingPage />} />
<Route path='/home' element={<RequireAuth><HomePage /></RequireAuth>} />
<Route path='/setup' element={<RequireAuth><Setup /></RequireAuth>} />
<Route path='*' element={<RequireAuth><NotFound /></RequireAuth>} />
</Routes>
</Router>
</PlaylistContextProvider>
</AuthContextProvider>
)}
</>
);
}
export default App;
AuthContext.tsx
import { useState, useEffect, createContext, useContext } from 'react'
import { useFuncContext } from './FuncContext'
type authDataType = {
id?: string,
username?: string,
displayName?: string,
profileUrl?: string,
profileImage?: string
}
type contextType = {
authData: authDataType,
onLogout: () => void
}
type contextProviderProps = {
children: React.ReactElement
}
const AuthContext = createContext<contextType | undefined>(undefined);
const AuthContextProvider = ({ children }: contextProviderProps) => {
const [authData, setAuthData] = useState<authDataType | {}>({});
const func = useFuncContext();
useEffect(() => {
console.log('AuthContext')
const getLoggedInUser = async () => {
try {
const response = await fetch('/auth/user', {
method: 'GET',
headers: {
Accept: 'application/json'
},
credentials: 'include'
})
const data = await response.json();
if (!response.ok || data.status === 'error') {
func?.setError(data.message);
throw new Error(data.message);
}
setAuthData(data);
return data;
} catch (error) {
throw new Error('Cannot get logged in user')
}
}
getLoggedInUser()
}, [])
const onLogout = () => setAuthData({});
return (
<AuthContext.Provider value={{ authData, onLogout }}>
{children}
</AuthContext.Provider>
)
}
const useAuthContext = () => useContext(AuthContext);
export { AuthContextProvider, useAuthContext };
RequireAuth.tsx
import React, { useEffect } from 'react'
import { Navigate } from 'react-router-dom';
import { useAuthContext } from '../contexts/AuthContext';
import { useFuncContext } from '../contexts/FuncContext';
type reqAuthProps = {
children: React.ReactElement
}
const RequireAuth = ({ children }: reqAuthProps) => {
const auth = useAuthContext();
const func = useFuncContext()
useEffect(() => {
console.log('RequireAuth')
if (!auth?.authData.id) func?.setError('Please log in to access this page')
}, [])
if (!auth?.authData.id) {
return <Navigate to='/' replace />
}
return children;
}
export default RequireAuth;