Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add deepMerge Support for Merging Nested Arrays and Objects in Props #2069

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/core/src/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ export class Response {
protected mergeProps(pageResponse: Page): void {
if (this.requestParams.isPartial() && pageResponse.component === currentPage.get().component) {
const propsToMerge = pageResponse.mergeProps || []
const propsToDeepMerge = pageResponse.deepMergeProps || []

propsToMerge.forEach((prop) => {
const incomingProp = pageResponse.props[prop]
Expand All @@ -221,6 +222,31 @@ export class Response {
}
})

propsToDeepMerge.forEach((prop) => {
const incomingProp = pageResponse.props[prop];
const currentProp = currentPage.get().props[prop];

// Deep merge function to handle nested objects and arrays
const deepMerge = (target: any, source: any) => {
if (Array.isArray(source)) {
// Merge arrays by concatenating the existing and incoming elements
return [...(Array.isArray(target) ? target : []), ...source];
} else if (typeof source === 'object' && source !== null) {
// Merge objects by iterating over keys
return Object.keys(source).reduce((acc, key) => {
acc[key] = deepMerge(target ? target[key] : undefined, source[key]);
return acc;
}, { ...target });
}
// If the source is neither an array nor an object, return it directly
return source;
};

// Assign the deeply merged result back to props.
pageResponse.props[prop] = deepMerge(currentProp, incomingProp);
});


pageResponse.props = { ...currentPage.get().props, ...pageResponse.props }
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface Page<SharedProps extends PageProps = PageProps> {
encryptHistory: boolean
deferredProps?: Record<string, VisitOptions['only']>
mergeProps?: string[]
deepMergeProps?: string[]

/** @internal */
rememberedState: Record<string, unknown>
Expand Down
1 change: 1 addition & 0 deletions packages/vue3/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export function usePage<SharedProps extends PageProps>(): Page<SharedProps> {
clearHistory: computed(() => page.value?.clearHistory),
deferredProps: computed(() => page.value?.deferredProps),
mergeProps: computed(() => page.value?.mergeProps),
deepMergeProps: computed(() => page.value?.deepMergeProps),
rememberedState: computed(() => page.value?.rememberedState),
encryptHistory: computed(() => page.value?.encryptHistory),
})
Expand Down
27 changes: 19 additions & 8 deletions tests/app/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,25 @@ app.get('/when-visible', (req, res) => {
})

app.get('/merge-props', (req, res) => {
inertia.render(req, res, {
component: 'MergeProps',
props: {
bar: new Array(5).fill(1),
foo: new Array(5).fill(1),
},
...(req.headers['x-inertia-reset'] ? {} : { mergeProps: ['foo'] }),
})
inertia.render(req, res, {
component: 'MergeProps',
props: {
bar: new Array(5).fill(1),
foo: new Array(5).fill(1),
},
...(req.headers['x-inertia-reset'] ? {} : { mergeProps: ['foo'] }),
})
})

app.get('/deep-merge-props', (req, res) => {
inertia.render(req, res, {
component: 'MergeProps',
props: {
bar: new Array(5).fill(1),
foo: new Array(5).fill(1),
},
...(req.headers['x-inertia-reset'] ? {} : { deepMergeProps: ['foo'] }),
})
})

app.get('/deferred-props/page-1', (req, res) => {
Expand Down
29 changes: 29 additions & 0 deletions tests/deep-merge-props.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect, test } from '@playwright/test'
import { clickAndWaitForResponse } from './support'

test('can deep merge props', async ({ page }) => {
await page.goto('/deep-merge-props')

await expect(page.getByText('bar count is 5')).toBeVisible()
await expect(page.getByText('foo count is 5')).toBeVisible()

await clickAndWaitForResponse(page, 'Reload', null, 'button')

await expect(page.getByText('bar count is 5')).toBeVisible()
await expect(page.getByText('foo count is 10')).toBeVisible()

await clickAndWaitForResponse(page, 'Reload', null, 'button')

await expect(page.getByText('bar count is 5')).toBeVisible()
await expect(page.getByText('foo count is 15')).toBeVisible()

await clickAndWaitForResponse(page, 'Get Fresh', null, 'button')

await expect(page.getByText('bar count is 5')).toBeVisible()
await expect(page.getByText('foo count is 5')).toBeVisible()

await clickAndWaitForResponse(page, 'Reload', null, 'button')

await expect(page.getByText('bar count is 5')).toBeVisible()
await expect(page.getByText('foo count is 10')).toBeVisible()
})
Loading