Repository: remix-run/react-router
File: packages/react-router-dom/index.tsx (TypeScript)

Code before:

function deserializeErrors(
  errors: RemixRouter["state"]["errors"]
): RemixRouter["state"]["errors"] {
  if (!errors) return null;
  let entries = Object.entries(errors);
  let serialized: RemixRouter["state"]["errors"] = {};
  for (let [key, val] of entries) {
    // Hey you!  If you change this, please change the corresponding logic in
    // serializeErrors in react-router-dom/server.tsx :)
    if (val && val.__type === "RouteErrorResponse") {
      serialized[key] = new ErrorResponseImpl(
        val.status,
        val.statusText,
        val.data,
        val.internal === true
      );
    } else if (val && val.__type === "Error") {
      // Attempt to reconstruct the right type of Error (i.e., ReferenceError)
      if (val.__subType) {
        let ErrorConstructor = window[val.__subType];
        if (typeof ErrorConstructor === "function") {
          try {
            // @ts-expect-error
            let error = new ErrorConstructor(val.message);
            error.stack = val.stack;
            serialized[key] = error;
          } catch (e) {
            // no-op - fall through and create a normal Error
          }
        }
      }

      if (serialized[key] == null) {
        let error = new Error(val.message);
        error.stack = val.stack;
        serialized[key] = error;
      }
    } else {
      serialized[key] = val;
    }
  }
  return serialized;
}

//#endregion

////////////////////////////////////////////////////////////////////////////////
//#region Contexts
////////////////////////////////////////////////////////////////////////////////


Review comment (bug):
lol I was super confused for a bit - since we shouldn't be using this "built-in" hydration logic in Remix since we do our own hydration via `window.__remixContext`.  This is used to automatically hydrate from `StaticRouterProvider`'s `window.__staticRouterHydrationData` - but we specifically pass [`hydrate=false`](https://github.com/remix-run/react-router/blob/v7/packages/react-router-dom/ssr/server.tsx#L90) in the Remix SSR use case.

Turns out we're just using the wrong implementation since we brought the Remix code over.  There's a dup version of this method in `ssr/errors.ts` that preserves the stack that we're not implementing since it found a local function with the same name.  

The original reason for automatically clearing stack traces is that it felt safer than assuming any DIY-SSR setups would always be stripping them on the server like Remix does so it prevented any accidental leakage of SSR stack traces.  Including the in dev-only mode felt like something advanced users could achieve via manual hydration.

I think for now we can just de-dup them and maybe add a param to preserve the stack trace that we send from the Remix usage in `RouterProvider` and continue stripping in the RR case (`parseHydrationData`).

I am also pretty sure the Remix usage can go away in v7 with single fetch but would need to look a bit deeper into that.  

Code after:

function deserializeErrors(
  errors: RemixRouter["state"]["errors"]
): RemixRouter["state"]["errors"] {
  if (!errors) return null;
  let entries = Object.entries(errors);
  let serialized: RemixRouter["state"]["errors"] = {};
  for (let [key, val] of entries) {
    // Hey you!  If you change this, please change the corresponding logic in
    // serializeErrors in react-router-dom/server.tsx :)
    if (val && val.__type === "RouteErrorResponse") {
      serialized[key] = new ErrorResponseImpl(
        val.status,
        val.statusText,
        val.data,
        val.internal === true
      );
    } else if (val && val.__type === "Error") {
      // Attempt to reconstruct the right type of Error (i.e., ReferenceError)
      if (val.__subType) {
        let ErrorConstructor = window[val.__subType];
        if (typeof ErrorConstructor === "function") {
          try {
            // @ts-expect-error
            let error = new ErrorConstructor(val.message);
            error.stack =
              process.env.NODE_ENV === "development" ? val.stack : "";
            serialized[key] = error;
          } catch (e) {
            // no-op - fall through and create a normal Error
          }
        }
      }

      if (serialized[key] == null) {
        let error = new Error(val.message);
        error.stack = process.env.NODE_ENV === "development" ? val.stack : "";
        serialized[key] = error;
      }
    } else {
      serialized[key] = val;
    }
  }
  return serialized;
}

//#endregion

////////////////////////////////////////////////////////////////////////////////
//#region Contexts
////////////////////////////////////////////////////////////////////////////////