I have this simple hybrid SPA build with Astro, React, and RRDv6
Route tree:
˫ localhost/ ------------------(public page)
˫ localhost/login ------------(admin login page)
˪ localhost/admin/home ---(admin dashboard page)
When I have successfully logged in and redirected to admin dashboard page, no error or any warning is present. I tried to refresh the page to simulate refreshing dashboard to fetch latest data from database through API. Instead I am having this error in my console log and my page is rendered blank.
Uncaught Error:
useNavigate()
may be used only in the context of a<Router>
component.
Am I missing the RRDv6 concept, or missing something that is important?
env: node 16.13.0, npm 8.3.0
deps: @astrojs/react 2.0.2, astro 2.0.16, react 18.2.0, react-router-dom 6.11.1
script: dev = astro dev
cmd: npm run dev
Routes:
export default function Admin() {
const [cookie, setCookie, removeCookie] = useCookies(['token']);
const PrivateRoute = () => {
const location = useLocation()
const isAuthenticated = () => {
return cookie.token !== null && cookie.token !== undefined;
};
return isAuthenticated()
? <Outlet />
: <Navigate to="/admin" replace state={{ from: location }} />;
};
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<AdminLogin />} />
<Route path="/admin" element={<PrivateRoute />}>
<Route path="home" element={<AdminHome />} />
</Route>
</Routes>
</BrowserRouter>
);
}
Admin Dashboard:
export default function AdminHome() {
const [cookie, setCookie, removeCookie] = useCookies(['token']);
const [doLogout, setDoLogout] = useState(false)
const navigate= useNavigate()
useEffect(() => {
if (doLogout) {
removeCookie('token', { sameSite: 'none', secure: true })
navigate('/login')
}
}, [doLogout])
return (
<div>
<AdminAppbar cookie={cookie} logout={setDoLogout} />
<AdminBlog/>
</div>
);
}
The only issues I see in the code are:
The PrivateRoute
component is declared inside another React component. Each time the parent component renders, a new "instance" of PrivateRoute
is created and that component's entire ReactTree is torn down and the new "instance" mounted. React components generally should be declared on their own.
The protected route is redirecting to itself when the isAuthenticated
evaluates falsey. This is likely creating a render loop which is why the page is blank. Routes you are protecting should not redirect to other protected routes.
Update PrivateRoute
to redirect to the authentication route.
const PrivateRoute = () => {
const location = useLocation();
const [cookie, setCookie, removeCookie] = useCookies(['token']);
const isAuthenticated = cookie.token !== null && cookie.token !== undefined;
return isAuthenticated
? <Outlet />
: (
<Navigate
to="/login" // <-- redirect to non-protected route
replace
state={{ from: location }}
/>
);
};
export default function Admin() {
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<AdminLogin />} />
<Route path="/admin" element={<PrivateRoute />}>
<Route path="home" element={<AdminHome />} />
</Route>
</Routes>
</BrowserRouter>
);
}