Repository: TanStack/router (ts)

Issue:
Error propagated from child-route to parent
### Describe the bug

I have 2 routes:
- `/$slug` which displays the detail of an entity
- `/$slug/report` which displays the report for the entity

Bot have `loader` and `errorComponent` on the routes as they have to load some data to display and handle errors. If an exception is thrown on `/$slug/report` route it displays the error from `errorComponent` which is to be expected. But if I navigate (via `Link`) to the detail `/$slug` it also shows the `errorComponent` with the same error as was thrown in the report route.

### Your Example Website or App

https://stackblitz.com/edit/github-3mpcwb?file=src%2Fmain.tsx

### Steps to Reproduce the Bug or Issue

1. Click on "Report"
2. See error thrown on Report route
3. Click on "Detail"
4. See error thrown on Detail route

### Expected behavior

I would expect the "Detail" route to not thrown an error as none was thrown in it.

### Screenshots or Videos

https://github.com/user-attachments/assets/acf445a9-cd93-4c04-9686-d5d0498f727e

### Platform

- OS: macOS
- Browser: Chrome
- Version: 127.0.6533.72


### Additional context

Hopefully I didn't miss some obvious 
Failed to reset error component when defaultErrorComponent is set
### Describe the bug

I can't reset error component through navigation when defaultErrorComponent is set

### Your Example Website or App

https://stackblitz.com/edit/vitejs-vite-7thcsy

### Steps to Reproduce the Bug or Issue

1. Click on the link `About`
2. Error will show up on `About` page
3. Click on the link `Home`
4. Error persist on `Home` page
5. Click on the link `Home` again - error disappears

### Expected behavior

1. Click on the link `About`
2. Error will show up on `About` page
3. Click on the link `Home`
4. Error disappears and `Home` page content should be displayed

### Screenshots or Videos

_No response_

### Platform

- OS: macOS
- Browser: Chrome
- Version: 127.0

### Additional context

Try to remove or comment out defaultErrorComponent - now it's working as expected

Pull request:
fix(react-router): reset error boundary after the new matches are ready
In the current version of the router, the error boundary is reset at the beginning of the loading phase of the new matching routes. This is a problem because the currently rendered routes can re-render while the new ones ar being in the loading process. If this is the case, the re-render will trigger the error boundary again and will void the reset.

I believe the reset should happen only after the new routes have finished loading. In this way, the reset will be in sync with the matched routes.

I don't know if this fix might have some side effects, but on our project it seems to work. If you think that this fix is not ok or you have a better solution, I would be happy to try it out.

Here is the issue I am referring to: https://github.com/TanStack/router/issues/2162

fixes #2162
fixes #2043

Patch:
diff --git a/packages/react-router/src/router.ts b/packages/react-router/src/router.ts
index cb07523e9..ff26c3bcb 100644
--- a/packages/react-router/src/router.ts
+++ b/packages/react-router/src/router.ts
@@ -1643,11 +1643,6 @@ export class Router<
   load = async (): Promise<void> => {
     this.latestLocation = this.parseLocation(this.latestLocation)
 
-    this.__store.setState((s) => ({
-      ...s,
-      loadedAt: Date.now(),
-    }))
-
     let redirect: ResolvedRedirect | undefined
     let notFound: NotFoundError | undefined
 
@@ -1738,6 +1733,7 @@ export class Router<
                     return {
                       ...s,
                       isLoading: false,
+                      loadedAt: Date.now(),
                       matches: newMatches,
                       pendingMatches: undefined,
                       cachedMatches: [


Test patch:
diff --git a/packages/react-router/tests/link.test.tsx b/packages/react-router/tests/link.test.tsx
index 9bf54202b..07a47d109 100644
--- a/packages/react-router/tests/link.test.tsx
+++ b/packages/react-router/tests/link.test.tsx
@@ -760,6 +760,77 @@ describe('Link', () => {
     expect(onError).toHaveBeenCalledOnce()
   })
 
+  test('when navigating away from a route with a loader that errors', async () => {
+    const postsOnError = vi.fn()
+    const indexOnError = vi.fn()
+    const rootRoute = createRootRoute({
+      component: () => (
+        <>
+          <div>
+            <Link to="/">Index</Link> <Link to="/posts">Posts</Link>
+          </div>
+          <hr />
+          <Outlet />
+        </>
+      ),
+    })
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => {
+        return (
+          <>
+            <h1>Index</h1>
+          </>
+        )
+      },
+      onError: indexOnError,
+      errorComponent: () => <span>IndexError</span>,
+    })
+
+    const error = new Error('Something went wrong!')
+
+    const postsRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: 'posts',
+      loaderDeps: (opts) => ({ page: opts.search }),
+      loader: () => {
+        throw error
+      },
+      onError: postsOnError,
+      errorComponent: () => <span>PostsError</span>,
+      component: () => {
+        return (
+          <>
+            <h1>Posts</h1>
+          </>
+        )
+      },
+    })
+
+    const router = createRouter({
+      routeTree: rootRoute.addChildren([indexRoute, postsRoute]),
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const postsLink = await screen.findByRole('link', { name: 'Posts' })
+
+    fireEvent.click(postsLink)
+
+    const postsErrorText = await screen.findByText('PostsError')
+    expect(postsErrorText).toBeInTheDocument()
+
+    expect(postsOnError).toHaveBeenCalledOnce()
+    expect(postsOnError).toHaveBeenCalledWith(error)
+
+    const indexLink = await screen.findByRole('link', { name: 'Index' })
+    fireEvent.click(indexLink)
+
+    expect(screen.findByText('IndexError')).rejects.toThrow()
+    expect(indexOnError).not.toHaveBeenCalledOnce()
+  })
+
   test('when navigating to /posts with a beforeLoad that redirects', async () => {
     const rootRoute = createRootRoute()
     const indexRoute = createRoute({