diff --git a/packages/core/src/page.ts b/packages/core/src/page.ts index 9d7a1ddb5..b017ce3d1 100644 --- a/packages/core/src/page.ts +++ b/packages/core/src/page.ts @@ -2,16 +2,7 @@ import { eventHandler } from './eventHandler' import { fireNavigateEvent } from './events' import { history } from './history' import { Scroll } from './scroll' -import { - Component, - Page, - PageEvent, - PageHandler, - PageResolver, - PreserveStateOption, - RouterInitParams, - VisitOptions, -} from './types' +import { Component, Page, PageEvent, PageHandler, PageResolver, RouterInitParams } from './types' import { hrefToUrl, isSameUrlWithoutHash } from './url' class CurrentPage { @@ -41,7 +32,11 @@ class CurrentPage { replace = false, preserveScroll = false, preserveState = false, - }: Partial> = {}, + }: { + replace?: boolean + preserveScroll?: boolean + preserveState?: boolean + } = {}, ): Promise { if (Object.keys(page.deferredProps || {}).length) { this.pendingDeferredProps = { @@ -123,7 +118,7 @@ class CurrentPage { { preserveState = false, }: { - preserveState?: PreserveStateOption + preserveState?: boolean } = {}, ) { return this.resolve(page.component).then((component) => { @@ -167,7 +162,7 @@ class CurrentPage { }: { component: Component page: Page - preserveState: PreserveStateOption + preserveState: boolean }): Promise { return this.swapComponent({ component, page, preserveState }) } diff --git a/packages/core/src/requestParams.ts b/packages/core/src/requestParams.ts index b815ff662..91d898cfb 100644 --- a/packages/core/src/requestParams.ts +++ b/packages/core/src/requestParams.ts @@ -138,8 +138,8 @@ export class RequestParams { } public setPreserveOptions(page: Page) { - this.params.preserveScroll = this.resolvePreserveOption(this.params.preserveScroll, page) - this.params.preserveState = this.resolvePreserveOption(this.params.preserveState, page) + this.params.preserveScroll = RequestParams.resolvePreserveOption(this.params.preserveScroll, page) + this.params.preserveState = RequestParams.resolvePreserveOption(this.params.preserveState, page) } public runCallbacks() { @@ -169,7 +169,7 @@ export class RequestParams { this.callbacks.push({ name, args }) } - protected resolvePreserveOption(value: PreserveStateOption, page: Page): boolean { + public static resolvePreserveOption(value: PreserveStateOption, page: Page): boolean { if (typeof value === 'function') { return value(page) } diff --git a/packages/core/src/response.ts b/packages/core/src/response.ts index 7fa353967..caf071d51 100644 --- a/packages/core/src/response.ts +++ b/packages/core/src/response.ts @@ -175,8 +175,8 @@ export class Response { return currentPage.set(pageResponse, { replace: this.requestParams.all().replace, - preserveScroll: this.requestParams.all().preserveScroll, - preserveState: this.requestParams.all().preserveState, + preserveScroll: this.requestParams.all().preserveScroll as boolean, + preserveState: this.requestParams.all().preserveState as boolean, }) } diff --git a/packages/core/src/router.ts b/packages/core/src/router.ts index 5e8db85d9..9ede2173f 100644 --- a/packages/core/src/router.ts +++ b/packages/core/src/router.ts @@ -8,6 +8,7 @@ import { page as currentPage } from './page' import { polls } from './polls' import { prefetchedRequests } from './prefetched' import { Request } from './request' +import { RequestParams } from './requestParams' import { RequestStream } from './requestStream' import { Scroll } from './scroll' import { @@ -386,19 +387,21 @@ export class Router { const { onError, onFinish, onSuccess, ...pageParams } = params + const page = { + ...current, + ...pageParams, + props: props as Page['props'], + } + + const preserveScroll = RequestParams.resolvePreserveOption(params.preserveScroll ?? false, page) + const preserveState = RequestParams.resolvePreserveOption(params.preserveState ?? false, page) + currentPage - .set( - { - ...current, - ...pageParams, - props: props as Page['props'], - }, - { - replace, - preserveScroll: params.preserveScroll, - preserveState: params.preserveState, - }, - ) + .set(page, { + replace, + preserveScroll, + preserveState, + }) .then(() => { const errors = currentPage.get().props.errors || {} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index ef51c49ce..ea80f1516 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -163,7 +163,7 @@ export type PageHandler = ({ }: { component: Component page: Page - preserveState: PreserveStateOption + preserveState: boolean }) => Promise export type PreserveStateOption = boolean | 'errors' | ((page: Page) => boolean) diff --git a/packages/react/test-app/Pages/ClientSideVisit/Page1.tsx b/packages/react/test-app/Pages/ClientSideVisit/Page1.tsx index 929635be5..05336f5bc 100644 --- a/packages/react/test-app/Pages/ClientSideVisit/Page1.tsx +++ b/packages/react/test-app/Pages/ClientSideVisit/Page1.tsx @@ -11,6 +11,7 @@ export default ({ foo, bar }: PageProps) => { const [errors, setErrors] = useState(0) const [finished, setFinished] = useState(0) const [success, setSuccess] = useState(0) + const [random, setRandom] = useState(Math.random()) const bagErrors = () => { router.replace({ @@ -46,6 +47,13 @@ export default ({ foo, bar }: PageProps) => { }) } + const replaceAndPreserveStateWithErrors = (errors = {}) => { + router.replace({ + preserveState: 'errors', + props: (props: PageProps) => ({ ...props, errors }), + }) + } + const push = () => { router.push({ url: '/client-side-visit-2', @@ -59,12 +67,17 @@ export default ({ foo, bar }: PageProps) => {
{foo}
{bar}
+ +
Errors: {errors}
Finished: {finished}
Success: {success}
+
Random: {random}
) } diff --git a/packages/svelte/test-app/Pages/ClientSideVisit/Page1.svelte b/packages/svelte/test-app/Pages/ClientSideVisit/Page1.svelte index 103d535ad..855af86df 100644 --- a/packages/svelte/test-app/Pages/ClientSideVisit/Page1.svelte +++ b/packages/svelte/test-app/Pages/ClientSideVisit/Page1.svelte @@ -13,6 +13,7 @@ let errors = 0 let finished = 0 let success = 0 + let random = Math.random() const bagErrors = () => { router.replace({ @@ -48,6 +49,13 @@ }) } + const replaceAndPreserveStateWithErrors = (errors = {}) => { + router.replace({ + preserveState: 'errors', + props: (props: PageProps) => ({ ...props, errors }), + }) + } + const push = () => { router.push({ url: '/client-side-visit-2', @@ -61,10 +69,15 @@
{foo}
{bar}
+ +
Errors: {errors}
Finished: {finished}
Success: {success}
+
Random: {random}
diff --git a/packages/vue3/test-app/Pages/ClientSideVisit/Page1.vue b/packages/vue3/test-app/Pages/ClientSideVisit/Page1.vue index 9c3d5a9d4..6356434be 100644 --- a/packages/vue3/test-app/Pages/ClientSideVisit/Page1.vue +++ b/packages/vue3/test-app/Pages/ClientSideVisit/Page1.vue @@ -13,6 +13,7 @@ const props = defineProps() const errors = ref(0) const finished = ref(0) const success = ref(0) +const random = ref(Math.random()) const bagErrors = () => { router.replace({ @@ -56,6 +57,13 @@ const replace = () => { }) } +const replaceAndPreserveStateWithErrors = (errors = {}) => { + router.replace({ + preserveState: 'errors', + props: (props: PageProps) => ({ ...props, errors }), + }) +} + const push = () => { router.push({ url: '/client-side-visit-2', @@ -71,10 +79,13 @@ const push = () => {
{{ foo }}
{{ bar }}
+ +
Errors: {{ errors }}
Finished: {{ finished }}
Success: {{ success }}
+
Random: {{ random }}
diff --git a/tests/client-side-visits.spec.ts b/tests/client-side-visits.spec.ts index 24d4fae21..e923127d8 100644 --- a/tests/client-side-visits.spec.ts +++ b/tests/client-side-visits.spec.ts @@ -14,7 +14,7 @@ test('replaces the page client side', async ({ page }) => { await expect(page.getByText('Finished: 0')).toBeVisible() await expect(page.getByText('Success: 0')).toBeVisible() - await page.getByRole('button', { name: 'Replace' }).click() + await page.getByRole('button', { name: 'Replace', exact: true }).click() await expect(page).toHaveURL('/client-side-visit') await expect(page.getByText('foo from server')).not.toBeVisible() @@ -30,6 +30,19 @@ test('replaces the page client side', async ({ page }) => { await expect(historyLength).toBe(2) }) +test('preserves the state based on the errors object', async ({ page }) => { + await page.goto('/client-side-visit') + const randomValue = await page.locator('#random').innerText() + + await page.getByRole('button', { name: 'Replace with errors' }).click() + const randomValueAfter = await page.locator('#random').innerText() + await expect(randomValueAfter).toBe(randomValue) + + await page.getByRole('button', { name: 'Replace without errors' }).click() + const randomValueAfterSecond = await page.locator('#random').innerText() + await expect(randomValueAfterSecond).not.toBe(randomValue) +}) + test('fires an onError callback when the props has errors', async ({ page }) => { pageLoads.watch(page)