javascriptreactjsreact-routerjsx

Why I'm getting error every time i try to access the protected route instead of redirect to /login?


I'm implementing protection route in my vanlife practice project and its not redirecting me it keep giving me this error when i try to access protected route

enter image description here

I'm supposed to get redirected to /login when I try to access the /host.

This is my main.jsx

import { createRoot } from 'react-dom/client'
import Layout from './components/Layout.jsx';
import Home from './pages/Home.jsx';
import About from './pages/About.jsx';
import Login from './pages/Login.jsx';
import Error from './components/Error.jsx';
import Vans, { loader as vanLoader } from './pages/Vans/Vans.jsx';
import VansDetails, { loader as vanDetailLoader } from './pages/Vans/VansDetails.jsx';
import Dashboard from './pages/Host/Dashboard.jsx';
import Income from './pages/Host/Income.jsx';
import Reviews from './pages/Host/Reviews.jsx';
import HostVans, { loader as hostvanLoader } from './pages/Host/HostVans.jsx';
import HostVansDetail, { loader as hostvandetailLoader } from './pages/Host/HostVansDetail.jsx';
import Pricing from './pages/Host/HostVanDetails/Pricing.jsx';
import Photos from './pages/Host/HostVanDetails/Photos.jsx';
import Info from './pages/Host/HostVanDetails/Info.jsx';
import HostLayout from './components/HostLayout.jsx';
import {
    RouterProvider,
    Route,
    createBrowserRouter,
    createRoutesFromElements
} from 'react-router-dom';
import NotFound from './pages/NotFound.jsx';
import './server.js';
import { requireAuth } from './utils.js';
import { StrictMode } from 'react';

const router = createBrowserRouter(createRoutesFromElements(
    <Route element={<Layout />}>
        <Route index element={<Home />} />
        <Route path='about' element={<About />} />
        <Route path='vans' element={<Vans />} loader={vanLoader} errorElement={<Error />} />
        <Route path='vans/:id' element={<VansDetails />} loader={vanDetailLoader} />

        <Route path='host' element={<HostLayout />} >
            <Route
                index
                element={<Dashboard />}
                loader={async () => await requireAuth()}
            />
            <Route
                path='income'
                element={<Income />}
                loader={async () => await requireAuth()}
            />
            <Route
                path='reviews'
                element={<Reviews />}
                loader={async () => await requireAuth()}
            />
            <Route
                path='vans'
                element={<HostVans />}
                loader={async () => {
                    await requireAuth()
                    return hostvanLoader()
                }}
            />
            <Route
                path='vans/:id'
                element={<HostVansDetail />}
                loader={async (obj) => {
                    await requireAuth()
                    return hostvandetailLoader(obj)
                }}
            >
                <Route
                    index
                    element={<Info />}
                    loader={async () => await requireAuth()}
                />
                <Route
                    path='pricing'
                    element={<Pricing />}
                    loader={async () => await requireAuth()}
                />
                <Route
                    path='photos'
                    element={<Photos />}
                    loader={async () => await requireAuth()}
                />
            </Route>
        </Route>
        <Route path='login' element={<Login />} />
        <Route path='*' element={<NotFound />} />
    </Route>
));

function App() {
    return (
        <div className='bg-[#FFF7ED] h-screen font-[inter] relative flex flex-col overflow-x-hidden'>
            <RouterProvider router={router} HydrateFallback={<h1>Loading...</h1>} />
        </div>
    )
}

createRoot(document.getElementById('root')).render(
    <StrictMode>
        <App />
    </StrictMode>
)

This is my utils.js which is supposed to run by the loader and redirect me to /login

import { redirect } from "react-router-dom"

export async function requireAuth() {
    const isLoggedIn = false
    
    if (!isLoggedIn) {
        throw redirect("/login")
    }
}

Solution

  • Why is throw redirect('/login') not redirecting but rendering errorElement instead?

    I was working with React Router's data APIs and using throw redirect("/login") in my loader functions to protect routes. However, instead of redirecting to /login, React Router was rendering the errorElement for that route or showing an error response.

    I found that manually setting response.body = true after calling redirect() made it work (got this idea from this post answer of this guy https://stackoverflow.com/a/76852081/13784221 :

    import { redirect } from "react-router-dom";  
    export async function requireAuth() {   
        const isLoggedIn = false;   
        if (!isLoggedIn) {     
            const response = redirect("/login");     
            response.body = true; // 👈 This made it work!     
            throw response;   
        }
        return null;
    } 
    

    Explanation: Why this works (based on React Router internals)

    This issue is related to how React Router internally handles thrown Response objects in loaders and actions. It distinguishes between:

    When I throw redirect("/login"), it creates a Response object with status = 302 and no body. Now, when you also provide an errorElement, React Router plays safe and tries to render it unless it's very sure you're doing a proper redirect.

    React Router checks for:

    error instanceof Response && error.status >= 300 && error.status < 400 && error.bodyUsed 
    

    So if bodyUsed is false, it falls back to showing the errorElement.

    The Hacky Fix: Setting response.body = true

    Setting response.body = true (or even response.bodyUsed = true) tricks React Router into treating your Response object as “used” and safe to redirect.

    So this:

    const response = redirect("/login"); 
    response.body = true; 
    throw response;
    

    ...acts as if the redirect body has been processed, and skips rendering the errorElement.