javascriptreactjsreact-routermiragejs

Issue with redirect React Router 6 and MirageJS. Why is my redirect throwing this error?


I'm currently building the Vanlife app for my https://scrimba.com/learn/reactrouter6 React Router 6 course on Scrimba and I've ran into an interesting issue (same as this user actually https://www.reddit.com/r/react/comments/13hdttx/help_me_with_react_router/?sort=new).

We currently have an utility function used for protecting a route and redirecting a user in a separate util.js file:

import { redirect } from "react-router";
export async function requireAuth() { 
    const isLoggedIn = false; 
    if (!isLoggedIn) { 
    throw redirect("/login");     
}}

And in our App.jsx file, we import the function and use it in the loader of the route we desire to protect

import { requireAuth } from "./utils";

export default function App(){
const router = createBrowserRouter(createRoutesFromElements(
    <Route path="/" element={<Layout />}>
        <Route path="login" element={<Login />} />
        <Route path="host" element={<HostLayout />}>  
             <Route loader={async () => await requireAuth()} index 
element={<Dashboard/>}/> </Route> </Route> )) }

Now, this all works on paper and, after running into this issue, I've even recreated the project in a functioning version but I've noticed that after introducing Mirage JS into the mix it all goes to hell.

This is the mysterious error returned by the React error handler:

error


Solution

  • It is perfectly acceptable, according to the react router docs, to throw a response object in a loader. This link to the docs states it and gives a simple example. Since a redirect returns a response object, it can also be thrown.

    This particular issue is because of miragejs. The error that renders is a polyfill response object that is monkey patched by another package mirage uses called pretenderJS. Pretender uses a package that created their own response prototype that constructs the responses made by redirect or a response constructor. Since the file is made globally available to the application, the responses constructed by redirect or calling new Response traverses the prototype chain of the polyfill response instead of creating a native response object.

    In react router 6.4.4 and below, I believe, they were checking differently for the response than in later versions.

    @6.4.4

    if(value instanceof Response){
      //passes
      //get location and redirect
    }
    

    Later than 6.4.4

    if(isResponse(value)){
     //fails
     //doesn't get location and redirect
    }
    

    The isResponse utility function they use checks to make sure that certain properties exist for the response object and returns true if they do and false otherwise. The condition that fails in this scenario is value.body !== undefined. The body is undefined because pretender's response, more specifically this library's response doesn't support streaming, so a body property is not present in it. The error that is rendering to your screen is the polyfill response and even if you return it, it still fails the check and no redirect occurs.

    The quickest solution is to downgrade your react router version to work with miragejs. The other workaround is to do something like this in later versions:

    create a utility file

    import { redirect } from "react-router-dom"
    
    //create a function
    function mutateResponse(path){
     //redirect returns the patch response 
     let response = redirect(path)
     //body can be anything but undefined
     response.body = true 
     return response
    }
    export { mutateResponse as redirect }
    

    Then in your function or anywhere a redirect is being used:

    import { redirect } from <path-to-util>;
    export async function requireAuth() { 
        const isLoggedIn = false; 
        if (!isLoggedIn) { 
            throw redirect("/login");     
        }
        return null
    }
    

    In the requireAuth return null or some data because that is what a loader expects.

    The body can be assigned a value in the utility function because the response is fake. If moving away from mirage to some server and db implementation, that code should be removed along with the fake server import in your application and the actual redirect from react router should be used. A body is a read-only stream and the above code will error if trying to assign a value to the body of a real response.

    Note that this workaround still works with the current latest version (6.14.1) at the time of writing.