Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nine-years-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/solid-router': patch
---

Fix for useLoaderData sometimes going undefined
13 changes: 12 additions & 1 deletion packages/solid-router/src/useMatch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,18 @@ export function useMatch<
return Solid.createMemo((prev: TSelected | undefined) => {
const selectedMatch = match()

if (selectedMatch === undefined) return undefined
if (selectedMatch === undefined) {
const hasPendingMatch = opts.from
? Boolean(router.stores.pendingRouteIds.get()[opts.from!])
: (nearestMatch?.hasPending() ?? false)

if (prev !== undefined && (hasPendingMatch || router.stores.isTransitioning.get())) {
return prev
}

return undefined
}

const res = opts.select ? opts.select(selectedMatch as any) : selectedMatch
if (prev === undefined) return res as TSelected
return replaceEqualDeep(prev, res) as TSelected
Expand Down
44 changes: 44 additions & 0 deletions packages/solid-router/tests/loaders.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
createRootRoute,
createRoute,
createRouter,
useLoaderData,
useRouter,
} from '../src'

Expand Down Expand Up @@ -741,6 +742,49 @@ test('clears pendingTimeout when match resolves', async () => {
expect(fooPendingComponentOnMountMock).not.toHaveBeenCalled()
})

test('useLoaderData retains previous data while route match is pending', async () => {
const history = createMemoryHistory({ initialEntries: ['/app'] })
const rootRoute = createRootRoute({
component: () => {
const loaderData = useLoaderData({ from: '/app' })

return (
<>
<div data-testid="combined">{`${loaderData().length}:0`}</div>
<Outlet />
</>
)
},
})
const appRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/app',
loader: () => 'loaded',
component: () => <div>App route</div>,
})
const routeTree = rootRoute.addChildren([appRoute])
const router = createRouter({ routeTree, history })

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

expect(await screen.findByTestId('combined')).toHaveTextContent('6:0')

const appMatch = router.state.matches.find((match) => match.routeId === '/app')

expect(appMatch).toBeDefined()

if (!appMatch) {
throw new Error('Expected /app match to be active')
}

router.stores.setPending([{ ...appMatch, id: `${appMatch.id}__pending` }])
router.stores.setMatches(
router.state.matches.filter((match) => match.routeId !== '/app'),
)

expect(screen.getByTestId('combined')).toHaveTextContent('6:0')
})

test('cancelMatches after pending timeout', async () => {
function getPendingComponent(onMount: () => void) {
const PendingComponent = () => {
Expand Down
Loading