Skip to content

Commit 40f456e

Browse files
jiji-hoon96schiller-manuelautofix-ci[bot]
authored
fix: Preserve cached data during concurrent navigation and refetch (#4513)
Co-authored-by: Manuel Schiller <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent be88e4c commit 40f456e

File tree

2 files changed

+83
-2
lines changed

2 files changed

+83
-2
lines changed

packages/react-router/tests/loaders.test.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,77 @@ test('throw error from beforeLoad when navigating to route', async () => {
339339
const indexElement = await screen.findByText('fooErrorComponent')
340340
expect(indexElement).toBeInTheDocument()
341341
})
342+
343+
test('reproducer #4245', async () => {
344+
const LOADER_WAIT_TIME = 500
345+
const rootRoute = createRootRoute({})
346+
347+
const indexRoute = createRoute({
348+
getParentRoute: () => rootRoute,
349+
path: '/',
350+
loader: async () => {
351+
await sleep(LOADER_WAIT_TIME)
352+
return 'index'
353+
},
354+
355+
component: () => {
356+
const data = indexRoute.useLoaderData()
357+
return (
358+
<div>
359+
<Link to="/foo" data-testid="link-to-foo">
360+
foo
361+
</Link>
362+
{data}
363+
</div>
364+
)
365+
},
366+
})
367+
368+
const fooRoute = createRoute({
369+
getParentRoute: () => rootRoute,
370+
path: '/foo',
371+
component: () => (
372+
<Link to="/" data-testid="link-to-index">
373+
index
374+
</Link>
375+
),
376+
})
377+
378+
const routeTree = rootRoute.addChildren([indexRoute, fooRoute])
379+
const router = createRouter({ routeTree })
380+
381+
render(<RouterProvider router={router} />)
382+
// We wait for the initial loader to complete
383+
await router.load()
384+
const fooLink = await screen.findByTestId('link-to-foo')
385+
386+
expect(fooLink).toBeInTheDocument()
387+
388+
// We navigate to the foo route
389+
fireEvent.click(fooLink)
390+
391+
// We immediately see the content of the foo route
392+
const indexLink = await screen.findByTestId('link-to-index')
393+
expect(indexLink).toBeInTheDocument()
394+
395+
// We navigate to the index route
396+
fireEvent.click(indexLink)
397+
398+
// We immediately see the content of the index route because the stale data is still available
399+
const fooLink2 = await screen.findByTestId('link-to-foo')
400+
expect(fooLink2).toBeInTheDocument()
401+
402+
// We navigate to the foo route again
403+
fireEvent.click(fooLink2)
404+
405+
// We immediately see the content of the foo route
406+
const indexLink2 = await screen.findByTestId('link-to-index')
407+
expect(indexLink2).toBeInTheDocument()
408+
409+
// We navigate to the index route again
410+
fireEvent.click(indexLink2)
411+
412+
// We now should see the content of the index route immediately because the stale data is still available
413+
const fooLink3 = await screen.findByTestId('link-to-foo')
414+
expect(fooLink3).toBeInTheDocument()
415+
})

packages/router-core/src/router.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,8 +1790,15 @@ export class RouterCore<
17901790
location: this.latestLocation,
17911791
pendingMatches,
17921792
// If a cached moved to pendingMatches, remove it from cachedMatches
1793-
cachedMatches: s.cachedMatches.filter((d) => {
1794-
return !pendingMatches.find((e) => e.id === d.id)
1793+
cachedMatches: s.cachedMatches.filter((cachedMatch) => {
1794+
const pendingMatch = pendingMatches.find((e) => e.id === cachedMatch.id)
1795+
1796+
if (!pendingMatch) return true
1797+
1798+
return (
1799+
cachedMatch.status === 'success' &&
1800+
(cachedMatch.isFetching || cachedMatch.loaderData !== undefined)
1801+
)
17951802
}),
17961803
}))
17971804
}

0 commit comments

Comments
 (0)