Skip to content

Commit 17d9f15

Browse files
committed
client side only visits
1 parent cd5607f commit 17d9f15

10 files changed

Lines changed: 205 additions & 0 deletions

File tree

packages/core/src/router.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import { RequestStream } from './requestStream'
1111
import { Scroll } from './scroll'
1212
import {
1313
ActiveVisit,
14+
ClientSidePushOptions,
15+
ClientSideReplaceOptions,
16+
ClientSideVisitOptions,
1417
GlobalEvent,
1518
GlobalEventNames,
1619
GlobalEventResult,
@@ -255,6 +258,31 @@ export class Router {
255258
return history.decrypt()
256259
}
257260

261+
public replace(params: ClientSideReplaceOptions): void {
262+
const current = currentPage.get()
263+
264+
this.clientVisit(
265+
{
266+
...current,
267+
...params,
268+
props: params.props ? params.props(current.props) : current.props,
269+
},
270+
{ replace: true },
271+
)
272+
}
273+
274+
public push(params: ClientSidePushOptions): void {
275+
this.clientVisit(params)
276+
}
277+
278+
protected clientVisit(params: ClientSideVisitOptions, { replace = false }: { replace?: boolean } = {}): void {
279+
currentPage.set(params, {
280+
replace,
281+
preserveScroll: params.preserveScroll,
282+
preserveState: params.preserveState,
283+
})
284+
}
285+
258286
protected getPrefetchParams(href: string | URL, options: VisitOptions): ActiveVisit {
259287
return {
260288
...this.getPendingVisit(href, {

packages/core/src/types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@ export interface Page<SharedProps extends PageProps = PageProps> {
4949
rememberedState: Record<string, unknown>
5050
}
5151

52+
interface ClientSideVisitOptions {
53+
component?: Page['component']
54+
url?: Page['url']
55+
clearHistory?: Page['clearHistory']
56+
encryptHistory?: Page['encryptHistory']
57+
preserveScroll?: VisitOptions['preserveScroll']
58+
preserveState?: VisitOptions['preserveState']
59+
}
60+
61+
export interface ClientSideReplaceOptions extends ClientSideVisitOptions {
62+
props?: (props: Page['props']) => Page['props']
63+
}
64+
65+
export interface ClientSidePushOptions extends ClientSideVisitOptions {
66+
props?: Page['props']
67+
}
68+
5269
export type PageResolver = (name: string) => Component
5370

5471
export type PageHandler = ({
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { router } from '@inertiajs/vue3'
2+
3+
export default ({ foo, bar }) => {
4+
const replace = () => {
5+
router.replace({
6+
props: (props) => ({ ...props, foo: 'foo from client' }),
7+
})
8+
}
9+
10+
const push = () => {
11+
router.push({
12+
url: '/client-side-visit-2',
13+
component: 'ClientSideVisit/Page2',
14+
props: { baz: 'baz from client' },
15+
})
16+
}
17+
18+
return (
19+
<div>
20+
<div>{foo}</div>
21+
<div>{bar}</div>
22+
<button onClick={replace}>Replace</button>
23+
<button onClick={push}>Push</button>
24+
</div>
25+
)
26+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default ({ baz }) => {
2+
return <div>{baz}</div>
3+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<script>
2+
import { router } from '@inertiajs/svelte'
3+
4+
export let foo;
5+
export let bar;
6+
7+
const replace = () => {
8+
router.replace({
9+
props: (props) => ({ ...props, foo: 'foo from client' }),
10+
});
11+
};
12+
13+
const push = () => {
14+
router.push({
15+
url: '/client-side-visit-2',
16+
component: 'ClientSideVisit/Page2',
17+
props: { baz: 'baz from client' },
18+
});
19+
};
20+
</script>
21+
22+
<div>
23+
<div>{foo}</div>
24+
<div>{bar}</div>
25+
<button on:click={replace}>Replace</button>
26+
<button on:click={push}>Push</button>
27+
</div>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
export let baz;
3+
</script>
4+
5+
<div>{baz}</div>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script setup lang="ts">
2+
import { router } from '@inertiajs/vue3'
3+
4+
defineProps<{
5+
foo: string
6+
bar: string
7+
}>()
8+
9+
const replace = () => {
10+
router.replace({
11+
props: (props) => ({ ...props, foo: 'foo from client' }),
12+
})
13+
}
14+
15+
const push = () => {
16+
router.push({
17+
url: '/client-side-visit-2',
18+
component: 'ClientSideVisit/Page2',
19+
props: {
20+
baz: 'baz from client',
21+
},
22+
})
23+
}
24+
</script>
25+
26+
<template>
27+
<div>{{ foo }}</div>
28+
<div>{{ bar }}</div>
29+
<button @click="replace">Replace</button>
30+
<button @click="push">Push</button>
31+
</template>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script setup lang="ts">
2+
defineProps<{
3+
baz: string
4+
}>()
5+
</script>
6+
7+
<template>
8+
<div>{{ baz }}</div>
9+
</template>

tests/app/server.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ app.get('/links/headers/version', (req, res) =>
7474
app.get('/links/data-loading', (req, res) => inertia.render(req, res, { component: 'Links/DataLoading' }))
7575
app.get('/links/prop-update', (req, res) => inertia.render(req, res, { component: 'Links/PropUpdate' }))
7676

77+
app.get('/client-side-visit', (req, res) =>
78+
inertia.render(req, res, {
79+
component: 'ClientSideVisit/Page1',
80+
props: { foo: 'foo from server', bar: 'bar from server' },
81+
}),
82+
)
83+
7784
app.get('/visits/partial-reloads', (req, res) =>
7885
inertia.render(req, res, {
7986
component: 'Visits/PartialReloads',

tests/client-side-visits.spec.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import test, { expect } from '@playwright/test'
2+
import { pageLoads, requests } from './support'
3+
4+
test('replaces the page client side', async ({ page }) => {
5+
pageLoads.watch(page)
6+
7+
await page.goto('/client-side-visit')
8+
9+
requests.listen(page)
10+
11+
await expect(page.getByText('foo from server')).toBeVisible()
12+
await expect(page.getByText('bar from server')).toBeVisible()
13+
await expect(page.getByText('foo from client')).not.toBeVisible()
14+
15+
await page.getByRole('button', { name: 'Replace' }).click()
16+
17+
await expect(page).toHaveURL('/client-side-visit')
18+
await expect(page.getByText('foo from server')).not.toBeVisible()
19+
await expect(page.getByText('foo from client')).toBeVisible()
20+
await expect(page.getByText('bar from server')).toBeVisible()
21+
22+
await expect(requests.requests.length).toBe(0)
23+
24+
const historyLength = await page.evaluate(() => window.history.length)
25+
26+
await expect(historyLength).toBe(2)
27+
})
28+
29+
test('pushes the page client side', async ({ page }) => {
30+
pageLoads.watch(page)
31+
32+
await page.goto('/client-side-visit')
33+
34+
requests.listen(page)
35+
36+
await expect(page.getByText('foo from server')).toBeVisible()
37+
await expect(page.getByText('bar from server')).toBeVisible()
38+
await expect(page.getByText('baz from client')).not.toBeVisible()
39+
40+
await page.getByRole('button', { name: 'Push' }).click()
41+
42+
await expect(page).toHaveURL('/client-side-visit-2')
43+
await expect(page.getByText('foo from server')).not.toBeVisible()
44+
await expect(page.getByText('bar from server')).not.toBeVisible()
45+
await expect(page.getByText('baz from client')).toBeVisible()
46+
47+
await expect(requests.requests.length).toBe(0)
48+
49+
const historyLength = await page.evaluate(() => window.history.length)
50+
51+
await expect(historyLength).toBe(3)
52+
})

0 commit comments

Comments
 (0)