Skip to content

Commit

Permalink
Merge pull request #2130 from inertiajs/client-only-visits
Browse files Browse the repository at this point in the history
[2.x] Client side visits
  • Loading branch information
joetannenbaum authored Dec 12, 2024
2 parents cd5607f + 074fd71 commit 29ff557
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 0 deletions.
28 changes: 28 additions & 0 deletions packages/core/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { RequestStream } from './requestStream'
import { Scroll } from './scroll'
import {
ActiveVisit,
ClientSideVisitOptions,
GlobalEvent,
GlobalEventNames,
GlobalEventResult,
Expand Down Expand Up @@ -255,6 +256,33 @@ export class Router {
return history.decrypt()
}

public replace(params: ClientSideVisitOptions): void {
this.clientVisit(params, { replace: true })
}

public push(params: ClientSideVisitOptions): void {
this.clientVisit(params)
}

protected clientVisit(params: ClientSideVisitOptions, { replace = false }: { replace?: boolean } = {}): void {
const current = currentPage.get()

const props = typeof params.props === 'function' ? params.props(current.props) : params.props ?? current.props

currentPage.set(
{
...current,
...params,
props,
},
{
replace,
preserveScroll: params.preserveScroll,
preserveState: params.preserveState,
},
)
}

protected getPrefetchParams(href: string | URL, options: VisitOptions): ActiveVisit {
return {
...this.getPendingVisit(href, {
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ export interface Page<SharedProps extends PageProps = PageProps> {
rememberedState: Record<string, unknown>
}

export interface ClientSideVisitOptions {
component?: Page['component']
url?: Page['url']
props?: ((props: Page['props']) => Page['props']) | Page['props']
clearHistory?: Page['clearHistory']
encryptHistory?: Page['encryptHistory']
preserveScroll?: VisitOptions['preserveScroll']
preserveState?: VisitOptions['preserveState']
}

export type PageResolver = (name: string) => Component

export type PageHandler = ({
Expand Down
26 changes: 26 additions & 0 deletions packages/react/test-app/Pages/ClientSideVisit/Page1.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { router } from '@inertiajs/react'

export default ({ foo, bar }) => {
const replace = () => {
router.replace({
props: (props) => ({ ...props, foo: 'foo from client' }),
})
}

const push = () => {
router.push({
url: '/client-side-visit-2',
component: 'ClientSideVisit/Page2',
props: { baz: 'baz from client' },
})
}

return (
<div>
<div>{foo}</div>
<div>{bar}</div>
<button onClick={replace}>Replace</button>
<button onClick={push}>Push</button>
</div>
)
}
3 changes: 3 additions & 0 deletions packages/react/test-app/Pages/ClientSideVisit/Page2.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default ({ baz }) => {
return <div>{baz}</div>
}
27 changes: 27 additions & 0 deletions packages/svelte/test-app/Pages/ClientSideVisit/Page1.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script>
import { router } from '@inertiajs/svelte'
export let foo;
export let bar;
const replace = () => {
router.replace({
props: (props) => ({ ...props, foo: 'foo from client' }),
});
};
const push = () => {
router.push({
url: '/client-side-visit-2',
component: 'ClientSideVisit/Page2',
props: { baz: 'baz from client' },
});
};
</script>

<div>
<div>{foo}</div>
<div>{bar}</div>
<button on:click={replace}>Replace</button>
<button on:click={push}>Push</button>
</div>
5 changes: 5 additions & 0 deletions packages/svelte/test-app/Pages/ClientSideVisit/Page2.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
export let baz;
</script>

<div>{baz}</div>
31 changes: 31 additions & 0 deletions packages/vue3/test-app/Pages/ClientSideVisit/Page1.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script setup lang="ts">
import { router } from '@inertiajs/vue3'
defineProps<{
foo: string
bar: string
}>()
const replace = () => {
router.replace({
props: (props) => ({ ...props, foo: 'foo from client' }),
})
}
const push = () => {
router.push({
url: '/client-side-visit-2',
component: 'ClientSideVisit/Page2',
props: {
baz: 'baz from client',
},
})
}
</script>

<template>
<div>{{ foo }}</div>
<div>{{ bar }}</div>
<button @click="replace">Replace</button>
<button @click="push">Push</button>
</template>
9 changes: 9 additions & 0 deletions packages/vue3/test-app/Pages/ClientSideVisit/Page2.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script setup lang="ts">
defineProps<{
baz: string
}>()
</script>

<template>
<div>{{ baz }}</div>
</template>
7 changes: 7 additions & 0 deletions tests/app/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ app.get('/links/headers/version', (req, res) =>
app.get('/links/data-loading', (req, res) => inertia.render(req, res, { component: 'Links/DataLoading' }))
app.get('/links/prop-update', (req, res) => inertia.render(req, res, { component: 'Links/PropUpdate' }))

app.get('/client-side-visit', (req, res) =>
inertia.render(req, res, {
component: 'ClientSideVisit/Page1',
props: { foo: 'foo from server', bar: 'bar from server' },
}),
)

app.get('/visits/partial-reloads', (req, res) =>
inertia.render(req, res, {
component: 'Visits/PartialReloads',
Expand Down
52 changes: 52 additions & 0 deletions tests/client-side-visits.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import test, { expect } from '@playwright/test'
import { pageLoads, requests } from './support'

test('replaces the page client side', async ({ page }) => {
pageLoads.watch(page)

await page.goto('/client-side-visit')

requests.listen(page)

await expect(page.getByText('foo from server')).toBeVisible()
await expect(page.getByText('bar from server')).toBeVisible()
await expect(page.getByText('foo from client')).not.toBeVisible()

await page.getByRole('button', { name: 'Replace' }).click()

await expect(page).toHaveURL('/client-side-visit')
await expect(page.getByText('foo from server')).not.toBeVisible()
await expect(page.getByText('foo from client')).toBeVisible()
await expect(page.getByText('bar from server')).toBeVisible()

await expect(requests.requests.length).toBe(0)

const historyLength = await page.evaluate(() => window.history.length)

await expect(historyLength).toBe(2)
})

test('pushes the page client side', async ({ page }) => {
pageLoads.watch(page)

await page.goto('/client-side-visit')

requests.listen(page)

await expect(page.getByText('foo from server')).toBeVisible()
await expect(page.getByText('bar from server')).toBeVisible()
await expect(page.getByText('baz from client')).not.toBeVisible()

await page.getByRole('button', { name: 'Push' }).click()

await expect(page).toHaveURL('/client-side-visit-2')
await expect(page.getByText('foo from server')).not.toBeVisible()
await expect(page.getByText('bar from server')).not.toBeVisible()
await expect(page.getByText('baz from client')).toBeVisible()

await expect(requests.requests.length).toBe(0)

const historyLength = await page.evaluate(() => window.history.length)

await expect(historyLength).toBe(3)
})

0 comments on commit 29ff557

Please sign in to comment.