I am trying to create my own custom authentication using React, AWS Amplify, and React Router V6, and my goal is to protect certain routes so users that are not logged in can't access them.
My code is here:
import './App.css';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import Navbar from './components/Navbar/Navbar';
import Dashboard from './components/Dashboard/Dashboard';
import Reports from './components/Reports/Reports';
import Patients from './components/Patients/Patients';
import { BrowserRouter, Navigate, Outlet, Route, Routes, useLocation, } from 'react-router-dom';
import React, { useEffect, useState } from 'react';
import '@aws-amplify/ui-react/styles.css';
import Signup from './components/Signup/Signup';
import Signin from './components/Signin/Signin';
import { Auth } from 'aws-amplify';
const darkTheme = createTheme({
palette: {
mode: 'dark',
}
});
const useAuth = () => {
const [user, setUser] = useState(null);
useEffect(() => {
const fetchUser = async () => {
const response = await Auth.currentAuthenticatedUser();
setUser(response);
}
fetchUser();
})
return { user };
}
function RequireAuth() {
const auth = useAuth();
const location = useLocation();
console.log(auth);
return (
auth
? <Outlet/>
: <Navigate to='/signin' state={{ from: location}} replace/>
);
}
const App = () => {
return (
<ThemeProvider theme={darkTheme}>
<BrowserRouter>
<Routes>
<Route element={<RequireAuth />}>
<Route path="/" element={<Navbar />}>
<Route path="dashboard" element={<Dashboard />} />
<Route path="patients" element={<Patients />} />
<Route path="reports" element={<Reports />} />
</Route>
</Route>
<Route path="/signin" element={<Signin />} />
<Route path="/signup" element={<Signup />} />
</Routes>
</BrowserRouter>
</ThemeProvider>
);
}
export default App;
I have spent the whole day trying countless methods but I keep getting the same results. What I'm trying to do is protect 3 routes Dashboard, Patients, and Reports. Whenever I click the sign-in button, I get around 500 logs of the current user as shown below:
Does anyone know what is triggering this component to re-render infinitely (hence executing my console.log(user) function), and is there any solution to fix this?
CodePudding user response:
It appears the useEffect
hook is missing a dependency array, so its callback is triggered each render cycle. Since the effect updates state, it triggers a rerender.
Add a dependency array.
useEffect(() => {
const fetchUser = async () => {
const response = await Auth.currentAuthenticatedUser();
setUser(response);
}
fetchUser();
}, []);
If you need to run this effect more than once when the routes are mounted, then you may need to add a dependency, like location
if/when the route path changes.
Example:
const { pathname } = useLocation();
useEffect(() => {
const fetchUser = async () => {
const response = await Auth.currentAuthenticatedUser();
setUser(response);
}
fetchUser();
}, [pathname]);
Since the useEffect
hook runs at the end of the initial render cycle you may want to also conditionally wait to render the outlet or redirect until the user
state is populated.
Example:
const useAuth = () => {
const [user, setUser] = useState(); // <-- initially undefined
useEffect(() => {
const fetchUser = async () => {
const response = await Auth.currentAuthenticatedUser();
setUser(response);
}
fetchUser();
}, []);
return { user };
}
...
function RequireAuth() {
const auth = useAuth();
const location = useLocation();
if (auth === undefined) {
return null; // or loading indicator, etc...
}
return (
auth
? <Outlet/>
: <Navigate to='/signin' state={{ from: location }} replace/>
);
}