Skip to content

Commit 8880d70

Browse files
authored
fix(ui): optimistic rows disappear while form state requests are pending (#11961)
When manipulating array and blocks rows on slow networks, rows can sometimes disappear and then reappear as requests in the queue arrive. Consider this scenario: 1. You add a row to form state: this pushes the row in local state optimistically then triggers a long-running form state request containing a single row 2. You add another row to form state: this pushes a second row into local state optimistically then triggers another long-running form state request containing two rows 3. The first form state request returns with a single row in the response and replaces local state (which contained two rows) 4. AT THIS MOMENT IN TIME, THE SECOND ROW DISAPPEARS 5. The second form state request returns with two rows in the response and replaces local state 6. THE UI IS NO LONGER STALE AND BOTH ROWS APPEAR AS EXPECTED The same issue applies when deleting, moving, and duplicating rows. Local state becomes out of sync with the form state response and is ultimately overridden. The issue is that when we merge the result from form state, we do not traverse the rows themselves, and instead take the rows in their entirety. This means that we lose local row state. Instead, we need to compare the results with what is saved to local state and intelligently merge them.
1 parent 018bdad commit 8880d70

File tree

2 files changed

+23
-3
lines changed

2 files changed

+23
-3
lines changed

packages/ui/src/forms/Form/mergeServerFormState.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
'use client'
2-
import type { FieldState } from 'payload'
2+
import type { FieldState, FormState } from 'payload'
33

44
import { dequal } from 'dequal/lite' // lite: no need for Map and Set support
5-
import { type FormState } from 'payload'
65

76
import { mergeErrorPaths } from './mergeErrorPaths.js'
87

@@ -34,7 +33,6 @@ export const mergeServerFormState = ({
3433
'valid',
3534
'errorMessage',
3635
'errorPaths',
37-
'rows',
3836
'customComponents',
3937
'requiresRender',
4038
]
@@ -77,6 +75,26 @@ export const mergeServerFormState = ({
7775
}
7876
}
7977

78+
/**
79+
* Need to intelligently merge the rows array to ensure no rows are lost or added while the request was pending
80+
* For example, the server response could come back with a row which has been deleted on the client
81+
* Loop over the incoming rows, if it exists in client side form state, merge in any new properties from the server
82+
*/
83+
if (Array.isArray(incomingState[path].rows)) {
84+
incomingState[path].rows.forEach((row) => {
85+
const matchedExistingRowIndex = newFieldState.rows.findIndex(
86+
(existingRow) => existingRow.id === row.id,
87+
)
88+
89+
if (matchedExistingRowIndex > -1) {
90+
newFieldState.rows[matchedExistingRowIndex] = {
91+
...newFieldState.rows[matchedExistingRowIndex],
92+
...row,
93+
}
94+
}
95+
})
96+
}
97+
8098
/**
8199
* Handle adding all the remaining props that should be updated in the local form state from the server form state
82100
*/

packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
316316

317317
acc.rows.push({
318318
id: row.id,
319+
isLoading: false,
319320
})
320321

321322
const previousRows = previousFormState?.[path]?.rows || []
@@ -495,6 +496,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
495496
acc.rowMetadata.push({
496497
id: row.id,
497498
blockType: row.blockType,
499+
isLoading: false,
498500
})
499501

500502
const collapsedRowIDs = preferences?.fields?.[path]?.collapsed

0 commit comments

Comments
 (0)