Skip to content

Commit f53a887

Browse files
committed
fix(solid-router): RouteProvider Wrap and Match InnerWrap support propagating context to their children
1 parent a98c89c commit f53a887

File tree

4 files changed

+161
-26
lines changed

4 files changed

+161
-26
lines changed

packages/solid-router/src/Matches.tsx

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,27 @@ declare module '@tanstack/router-core' {
3838
export function Matches() {
3939
const router = useRouter()
4040

41-
const pendingElement = router.options.defaultPendingComponent ? (
42-
<router.options.defaultPendingComponent />
43-
) : null
44-
4541
// Do not render a root Suspense during SSR or hydrating from SSR
4642
const ResolvedSuspense =
4743
router.isServer || (typeof document !== 'undefined' && router.ssr)
4844
? SafeFragment
4945
: Solid.Suspense
5046

51-
const inner = (
52-
<ResolvedSuspense fallback={pendingElement}>
53-
{!router.isServer && <Transitioner />}
54-
<MatchesInner />
55-
</ResolvedSuspense>
56-
)
47+
const OptionalWrapper = router.options.InnerWrap || SafeFragment
5748

58-
return router.options.InnerWrap ? (
59-
<router.options.InnerWrap>{inner}</router.options.InnerWrap>
60-
) : (
61-
inner
49+
return (
50+
<OptionalWrapper>
51+
<ResolvedSuspense
52+
fallback={
53+
router.options.defaultPendingComponent ? (
54+
<router.options.defaultPendingComponent />
55+
) : null
56+
}
57+
>
58+
{!router.isServer && <Transitioner />}
59+
<MatchesInner />
60+
</ResolvedSuspense>
61+
</OptionalWrapper>
6262
)
6363
}
6464

@@ -74,8 +74,10 @@ function MatchesInner() {
7474
select: (s) => s.loadedAt,
7575
})
7676

77-
const matchComponent = () =>
78-
matchId() ? <Match matchId={matchId()!} /> : null
77+
const matchComponent = () => {
78+
const id = matchId()
79+
return id ? <Match matchId={id} /> : null
80+
}
7981

8082
return (
8183
<matchContext.Provider value={matchId}>

packages/solid-router/src/RouterProvider.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Matches } from './Matches'
22
import { getRouterContext } from './routerContext'
3+
import { SafeFragment } from './SafeFragment'
34
import type * as Solid from 'solid-js'
45
import type {
56
AnyRouter,
@@ -29,17 +30,15 @@ export function RouterContextProvider<
2930

3031
const routerContext = getRouterContext()
3132

32-
const provider = (
33-
<routerContext.Provider value={router as AnyRouter}>
34-
{children()}
35-
</routerContext.Provider>
36-
)
37-
38-
if (router.options.Wrap) {
39-
return <router.options.Wrap>{provider}</router.options.Wrap>
40-
}
33+
const OptionalWrapper = router.options.Wrap || SafeFragment
4134

42-
return provider
35+
return (
36+
<OptionalWrapper>
37+
<routerContext.Provider value={router as AnyRouter}>
38+
{children()}
39+
</routerContext.Provider>
40+
</OptionalWrapper>
41+
)
4342
}
4443

4544
export function RouterProvider<

packages/solid-router/tests/Matches.test.tsx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import { expect, test } from 'vitest'
22
import { fireEvent, render, screen } from '@solidjs/testing-library'
3+
import { createContext, useContext } from 'solid-js'
34
import {
45
Link,
56
Outlet,
67
RouterProvider,
8+
createMemoryHistory,
79
createRootRoute,
810
createRoute,
911
createRouter,
1012
isMatch,
1113
useMatches,
1214
} from '../src'
15+
import { sleep } from './utils'
1316

1417
const rootRoute = createRootRoute()
1518

@@ -122,3 +125,99 @@ test('when filtering useMatches by loaderData', async () => {
122125

123126
expect(await screen.findByText('Incorrect Matches -')).toBeInTheDocument()
124127
})
128+
129+
test('Matches provides InnerWrap context to route components', async () => {
130+
const rootRoute = createRootRoute({
131+
component: () => {
132+
const contextValue = useContext(ctx)
133+
expect(contextValue, 'Context is not provided').not.toBeUndefined()
134+
135+
return <div>{contextValue}</div>
136+
},
137+
})
138+
139+
const routeTree = rootRoute.addChildren([])
140+
const router = createRouter({
141+
routeTree,
142+
})
143+
144+
const ctx = createContext<string>()
145+
146+
const screen = render(() => (
147+
<RouterProvider
148+
router={router}
149+
InnerWrap={(props) => {
150+
return (
151+
<ctx.Provider value={'context-for-children'}>
152+
{props.children}
153+
</ctx.Provider>
154+
)
155+
}}
156+
/>
157+
))
158+
159+
const indexElem = await screen.findByText('context-for-children')
160+
expect(indexElem).toBeInTheDocument()
161+
})
162+
163+
test('Matches provides InnerWrap context to defaultPendingComponent', async () => {
164+
const rootRoute = createRootRoute({})
165+
const indexRoute = createRoute({
166+
getParentRoute: () => rootRoute,
167+
path: '/',
168+
component: () => {
169+
return (
170+
<div>
171+
<Link to="/home">link to home</Link>
172+
</div>
173+
)
174+
},
175+
})
176+
177+
const homeRoute = createRoute({
178+
getParentRoute: () => rootRoute,
179+
path: '/home',
180+
loader: () => sleep(300),
181+
component: () => <div>Home page</div>,
182+
})
183+
184+
const routeTree = rootRoute.addChildren([homeRoute, indexRoute])
185+
const router = createRouter({
186+
routeTree,
187+
history: createMemoryHistory({
188+
initialEntries: ['/'],
189+
}),
190+
})
191+
192+
const ctx = createContext<string>()
193+
194+
const screen = render(() => (
195+
<RouterProvider
196+
router={router}
197+
defaultPendingMs={200}
198+
defaultPendingComponent={() => {
199+
const contextValue = useContext(ctx)
200+
expect(contextValue, 'Context is not provided').not.toBeUndefined()
201+
202+
return <div>{contextValue}</div>
203+
}}
204+
InnerWrap={(props) => {
205+
return (
206+
<ctx.Provider value={'context-for-default-pending'}>
207+
{props.children}
208+
</ctx.Provider>
209+
)
210+
}}
211+
/>
212+
))
213+
214+
const linkToHome = await screen.findByRole('link', {
215+
name: 'link to home',
216+
})
217+
expect(linkToHome).toBeInTheDocument()
218+
219+
fireEvent.click(linkToHome)
220+
221+
const indexElem = await screen.findByText('context-for-default-pending')
222+
expect(indexElem).toBeInTheDocument()
223+
})
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { render, screen } from '@solidjs/testing-library'
3+
import { createContext, useContext } from 'solid-js'
4+
import {
5+
createRootRoute,
6+
createRouter,
7+
} from '../src'
8+
import { RouterProvider } from '../src/RouterProvider'
9+
10+
describe('RouterProvider', () => {
11+
it('should provide context through RouterProvider Wrap', async () => {
12+
const rootRoute = createRootRoute({
13+
component: () => {
14+
const contextValue = useContext(ctx)
15+
expect(contextValue, "Context is not provided").not.toBeUndefined()
16+
17+
return <div>{contextValue}</div>
18+
},
19+
})
20+
21+
const routeTree = rootRoute.addChildren([])
22+
const router = createRouter({
23+
routeTree,
24+
})
25+
26+
const ctx = createContext<string>()
27+
28+
render(() => <RouterProvider router={router} Wrap={props => {
29+
return <ctx.Provider value={"findMe"}>{props.children}</ctx.Provider>
30+
}} />)
31+
32+
const indexElem = await screen.findByText('findMe')
33+
expect(indexElem).toBeInTheDocument()
34+
})
35+
})

0 commit comments

Comments
 (0)