Skip to content

Commit ba590c2

Browse files
authored
fix(solid-router): prevent client effects running on server (#4621)
1 parent aa1fb5d commit ba590c2

File tree

8 files changed

+97
-65
lines changed

8 files changed

+97
-65
lines changed

packages/react-router/src/Matches.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function Matches() {
5454

5555
const inner = (
5656
<ResolvedSuspense fallback={pendingElement}>
57-
<Transitioner />
57+
{!router.isServer && <Transitioner />}
5858
<MatchesInner />
5959
</ResolvedSuspense>
6060
)

packages/react-router/src/Transitioner.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,12 @@ export function Transitioner() {
3030
const isPagePending = isLoading || hasPendingMatches
3131
const previousIsPagePending = usePrevious(isPagePending)
3232

33-
if (!router.isServer) {
34-
router.startTransition = (fn: () => void) => {
35-
setIsTransitioning(true)
36-
React.startTransition(() => {
37-
fn()
38-
setIsTransitioning(false)
39-
})
40-
}
33+
router.startTransition = (fn: () => void) => {
34+
setIsTransitioning(true)
35+
React.startTransition(() => {
36+
fn()
37+
setIsTransitioning(false)
38+
})
4139
}
4240

4341
// Subscribe to location changes

packages/solid-router/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js -p tsconfig.legacy.json",
3434
"test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js -p tsconfig.legacy.json",
3535
"test:types:ts58": "tsc -p tsconfig.legacy.json",
36-
"test:unit": "vitest",
36+
"test:unit": "vitest && vitest --mode server",
3737
"test:unit:dev": "pnpm run test:unit --watch --hideSkippedTests",
3838
"test:perf": "vitest bench",
3939
"test:perf:dev": "pnpm run test:perf --watch --hideSkippedTests",

packages/solid-router/src/Matches.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function Matches() {
5050

5151
const inner = (
5252
<ResolvedSuspense fallback={pendingElement}>
53-
<Transitioner />
53+
{!router.isServer && <Transitioner />}
5454
<MatchesInner />
5555
</ResolvedSuspense>
5656
)

packages/solid-router/src/Transitioner.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,10 @@ export function Transitioner() {
3030
const isPagePending = () => isLoading() || hasPendingMatches()
3131
const previousIsPagePending = usePrevious(isPagePending)
3232

33-
if (!router.isServer) {
34-
router.startTransition = async (fn: () => void | Promise<void>) => {
35-
setIsTransitioning(true)
36-
await fn()
37-
setIsTransitioning(false)
38-
}
33+
router.startTransition = async (fn: () => void | Promise<void>) => {
34+
setIsTransitioning(true)
35+
await fn()
36+
setIsTransitioning(false)
3937
}
4038

4139
// Subscribe to location changes
@@ -66,7 +64,6 @@ export function Transitioner() {
6664

6765
// Try to load the initial location
6866
Solid.createRenderEffect(() => {
69-
if (router.isServer) return
7067
Solid.untrack(() => {
7168
if (
7269
// if we are hydrating from SSR, loading is triggered in ssr-client
@@ -100,6 +97,7 @@ export function Transitioner() {
10097
},
10198
),
10299
)
100+
103101
Solid.createRenderEffect(
104102
Solid.on(
105103
[isPagePending, previousIsPagePending],

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

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import { RouterProvider } from '../src/RouterProvider'
1010

1111
describe('Transitioner', () => {
1212
it('should call router.load() when Transitioner mounts on the client', async () => {
13+
const loader = vi.fn()
1314
const rootRoute = createRootRoute()
1415
const indexRoute = createRoute({
1516
getParentRoute: () => rootRoute,
1617
path: '/',
1718
component: () => <div>Index</div>,
19+
loader,
1820
})
1921

2022
const routeTree = rootRoute.addChildren([indexRoute])
@@ -26,43 +28,16 @@ describe('Transitioner', () => {
2628
})
2729

2830
// Mock router.load() to verify it gets called
29-
const loadSpy = vi.spyOn(router, 'load').mockResolvedValue(undefined)
31+
const loadSpy = vi.spyOn(router, 'load')
3032

31-
render(() => <RouterProvider router={router} />)
32-
33-
// Wait for the createRenderEffect to run and call router.load()
34-
await waitFor(() => {
35-
expect(loadSpy).toHaveBeenCalledTimes(1)
36-
})
37-
38-
loadSpy.mockRestore()
39-
})
40-
41-
it('should not call router.load() when on the server', async () => {
42-
const rootRoute = createRootRoute()
43-
const indexRoute = createRoute({
44-
getParentRoute: () => rootRoute,
45-
path: '/',
46-
component: () => <div>Index</div>,
47-
})
48-
49-
const routeTree = rootRoute.addChildren([indexRoute])
50-
const router = createRouter({
51-
routeTree,
52-
history: createMemoryHistory({
53-
initialEntries: ['/'],
54-
}),
55-
isServer: true,
56-
})
57-
58-
// Mock router.load() to verify it gets called
59-
const loadSpy = vi.spyOn(router, 'load').mockResolvedValue(undefined)
33+
await router.load()
6034

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

6337
// Wait for the createRenderEffect to run and call router.load()
6438
await waitFor(() => {
65-
expect(loadSpy).toHaveBeenCalledTimes(0)
39+
expect(loadSpy).toHaveBeenCalledTimes(2)
40+
expect(loader).toHaveBeenCalledTimes(1)
6641
})
6742

6843
loadSpy.mockRestore()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { describe, expect, it, vi } from 'vitest'
2+
import { renderToStringAsync } from 'solid-js/web'
3+
import {
4+
createMemoryHistory,
5+
createRootRoute,
6+
createRoute,
7+
createRouter,
8+
} from '../../src'
9+
import { RouterProvider } from '../../src/RouterProvider'
10+
11+
describe('Transitioner (server)', () => {
12+
it('should call router.load() only once when on the server', async () => {
13+
const loader = vi.fn()
14+
const rootRoute = createRootRoute()
15+
const indexRoute = createRoute({
16+
getParentRoute: () => rootRoute,
17+
path: '/',
18+
component: () => <div>Index</div>,
19+
loader,
20+
})
21+
22+
const routeTree = rootRoute.addChildren([indexRoute])
23+
const router = createRouter({
24+
routeTree,
25+
history: createMemoryHistory({
26+
initialEntries: ['/'],
27+
}),
28+
isServer: true,
29+
})
30+
31+
// Mock router.load() to verify it gets called
32+
const loadSpy = vi.spyOn(router, 'load')
33+
34+
await router.load()
35+
36+
await renderToStringAsync(() => <RouterProvider router={router} />)
37+
38+
expect(loadSpy).toHaveBeenCalledTimes(1)
39+
expect(loader).toHaveBeenCalledTimes(1)
40+
41+
loadSpy.mockRestore()
42+
})
43+
})

packages/solid-router/vite.config.ts

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,40 @@ import solid from 'vite-plugin-solid'
44
import packageJson from './package.json'
55
import type { ViteUserConfig } from 'vitest/config'
66

7-
const config = defineConfig({
8-
plugins: [solid()] as ViteUserConfig['plugins'],
9-
test: {
10-
name: packageJson.name,
11-
dir: './tests',
12-
watch: false,
13-
environment: 'jsdom',
14-
typecheck: { enabled: true },
15-
setupFiles: ['./tests/setupTests.tsx'],
16-
},
7+
const config = defineConfig(({ mode }) => {
8+
if (mode === 'server') {
9+
return {
10+
plugins: [solid({ ssr: true })] as ViteUserConfig['plugins'],
11+
test: {
12+
name: `${packageJson.name} (server)`,
13+
dir: './tests/server',
14+
watch: false,
15+
environment: 'node',
16+
typecheck: { enabled: true },
17+
},
18+
}
19+
}
20+
21+
return {
22+
plugins: [solid()] as ViteUserConfig['plugins'],
23+
test: {
24+
name: packageJson.name,
25+
dir: './tests',
26+
exclude: ['server'],
27+
watch: false,
28+
environment: 'jsdom',
29+
typecheck: { enabled: true },
30+
setupFiles: ['./tests/setupTests.tsx'],
31+
},
32+
}
1733
})
1834

19-
export default mergeConfig(
20-
config,
21-
tanstackViteConfig({
22-
entry: ['./src/index.tsx', './src/ssr/client.ts', './src/ssr/server.ts'],
23-
srcDir: './src',
24-
}),
35+
export default defineConfig((env) =>
36+
mergeConfig(
37+
config(env),
38+
tanstackViteConfig({
39+
entry: ['./src/index.tsx', './src/ssr/client.ts', './src/ssr/server.ts'],
40+
srcDir: './src',
41+
}),
42+
),
2543
)

0 commit comments

Comments
 (0)