Skip to content

Commit

Permalink
fix(react-router): pendingComponent not rendering with preload='int…
Browse files Browse the repository at this point in the history
…ent' (#2247)

* fix(router): pendingComponent not showing

* remove unused variable

* tests: add test
  • Loading branch information
MaciejGarncarski committed Sep 3, 2024
1 parent 8240405 commit fe08fb5
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 16 deletions.
41 changes: 26 additions & 15 deletions packages/react-router/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1971,12 +1971,38 @@ export class Router<
const existingMatch = this.getMatch(matchId)!
const parentMatchId = matches[index - 1]?.id

const route = this.looseRoutesById[routeId]!

const pendingMs =
route.options.pendingMs ?? this.options.defaultPendingMs

const shouldPending = !!(
onReady &&
!this.isServer &&
!preload &&
(route.options.loader || route.options.beforeLoad) &&
typeof pendingMs === 'number' &&
pendingMs !== Infinity &&
(route.options.pendingComponent ??
this.options.defaultPendingComponent)
)

if (
// If we are in the middle of a load, either of these will be present
// (not to be confused with `loadPromise`, which is always defined)
existingMatch.beforeLoadPromise ||
existingMatch.loaderPromise
) {
if (shouldPending) {
setTimeout(() => {
try {
// Update the match and prematurely resolve the loadMatches promise so that
// the pending component can start rendering
triggerOnReady()
} catch {}
}, pendingMs)
}

// Wait for the beforeLoad to resolve before we continue
await existingMatch.beforeLoadPromise
} else {
Expand All @@ -1990,23 +2016,8 @@ export class Router<
beforeLoadPromise: createControlledPromise<void>(),
}))

const route = this.looseRoutesById[routeId]!
const abortController = new AbortController()

const pendingMs =
route.options.pendingMs ?? this.options.defaultPendingMs

const shouldPending = !!(
onReady &&
!this.isServer &&
!preload &&
(route.options.loader || route.options.beforeLoad) &&
typeof pendingMs === 'number' &&
pendingMs !== Infinity &&
(route.options.pendingComponent ??
this.options.defaultPendingComponent)
)

let pendingTimeout: ReturnType<typeof setTimeout>

if (shouldPending) {
Expand Down
55 changes: 54 additions & 1 deletion packages/react-router/tests/link.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
useRouterState,
useSearch,
} from '../src'
import { getIntersectionObserverMock } from './utils'
import { getIntersectionObserverMock, sleep } from './utils'

const ioObserveMock = vi.fn()
const ioDisconnectMock = vi.fn()
Expand All @@ -47,6 +47,8 @@ afterEach(() => {
cleanup()
})

const WAIT_TIME = 300

describe('Link', () => {
test('when using renderHook it returns a hook with same content to prove rerender works', async () => {
/**
Expand Down Expand Up @@ -3599,6 +3601,57 @@ describe('Link', () => {

expect(ioObserveMock).not.toBeCalled()
})

test('Router.preload="intent", pendingComponent renders during unresolved route loader', async () => {
const rootRoute = createRootRoute()
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => {
return (
<div>
<h1>Index page</h1>
<Link to="/posts" preload="intent">
link to posts
</Link>
</div>
)
},
})

const postRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/posts',
loader: () => sleep(WAIT_TIME),
component: () => <div>Posts page</div>,
})

const routeTree = rootRoute.addChildren([postRoute, indexRoute])
const router = createRouter({
routeTree,
defaultPreload: 'intent',
defaultPendingMs: 200,
defaultPendingComponent: () => <p>Loading...</p>,
})

render(<RouterProvider router={router} />)

const linkToPosts = await screen.findByRole('link', {
name: 'link to posts',
})
expect(linkToPosts).toBeInTheDocument()

fireEvent.focus(linkToPosts)
fireEvent.click(linkToPosts)

const loadingElement = await screen.findByText('Loading...')
expect(loadingElement).toBeInTheDocument()

const postsElement = await screen.findByText('Posts page')
expect(postsElement).toBeInTheDocument()

expect(window.location.pathname).toBe('/posts')
})
})

describe('createLink', () => {
Expand Down

0 comments on commit fe08fb5

Please sign in to comment.