Repository: remix-run/react-router
File: packages/router/router.ts (TypeScript)

Code before:
  deletedFetchers: Set<string>,
  fetchLoadMatches: Map<string, FetchLoadMatch>,
  fetchRedirectIds: Set<string>,
  routesToUse: AgnosticDataRouteObject[],
  basename: string | undefined,
  pendingActionData?: RouteData,
  pendingError?: RouteData
): [AgnosticDataRouteMatch[], RevalidatingFetcher[]] {
  let actionResult = pendingError
    ? Object.values(pendingError)[0]
    : pendingActionData
    ? Object.values(pendingActionData)[0]
    : undefined;

  let currentUrl = history.createURL(state.location);
  let nextUrl = history.createURL(location);

  // Pick navigation matches that are net-new or qualify for revalidation
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);

  let navigationMatches = boundaryMatches.filter((match, index) => {
    if (isInitialLoad) {
      // On initial hydration we don't do any shouldRevalidate stuff - we just
      // call the unhydrated loaders
      return isUnhydratedRoute(state, match.route);
    }

    if (match.route.lazy) {
      // We haven't loaded this route yet so we don't know if it's got a loader!
      return true;
    }

    if (match.route.loader == null) {
      return false;
    }

    // Always call the loader on new route instances and pending defer cancellations
    if (
      isNewLoader(state.loaderData, state.matches[index], match) ||
      cancelledDeferredRoutes.some((id) => id === match.route.id)
    ) {
      return true;
    }

    // This is the default implementation for when we revalidate.  If the route
    // provides it's own implementation, then we give them full control but
    // provide this value so they can leverage it if needed after they check
    // their own specific use cases
    let currentRouteMatch = state.matches[index];
    let nextRouteMatch = match;

Review comment (suggestion):
This was backwards before - we should check lazy and the lack of a loader first, then look for unhydrated routes

Code after:
  deletedFetchers: Set<string>,
  fetchLoadMatches: Map<string, FetchLoadMatch>,
  fetchRedirectIds: Set<string>,
  routesToUse: AgnosticDataRouteObject[],
  basename: string | undefined,
  pendingActionData?: RouteData,
  pendingError?: RouteData
): [AgnosticDataRouteMatch[], RevalidatingFetcher[]] {
  let actionResult = pendingError
    ? Object.values(pendingError)[0]
    : pendingActionData
    ? Object.values(pendingActionData)[0]
    : undefined;

  let currentUrl = history.createURL(state.location);
  let nextUrl = history.createURL(location);

  // Pick navigation matches that are net-new or qualify for revalidation
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);

  let navigationMatches = boundaryMatches.filter((match, index) => {
    let { route } = match;
    if (route.lazy) {
      // We haven't loaded this route yet so we don't know if it's got a loader!
      return true;
    }

    if (route.loader == null) {
      return false;
    }

    if (isInitialLoad) {
      if (route.loader.hydrate) {
        return true;
      }
      return (
        state.loaderData[route.id] === undefined &&
        // Don't re-run if the loader ran and threw an error
        (!state.errors || state.errors[route.id] === undefined)
      );
    }

    // Always call the loader on new route instances and pending defer cancellations
    if (
      isNewLoader(state.loaderData, state.matches[index], match) ||
      cancelledDeferredRoutes.some((id) => id === match.route.id)
    ) {
      return true;
    }