Search code examples
javascriptreactjsreact-routerreact-router-domastrojs

Refreshing the page causes the page to be rendered blank and navigate() error is thrown


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>
  );
}

Solution

  • 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>
      );
    }