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.
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
'use client'
|
||||
import type { FieldState } from 'payload'
|
||||
import type { FieldState, FormState } from 'payload'
|
||||
|
||||
import { dequal } from 'dequal/lite' // lite: no need for Map and Set support
|
||||
import { type FormState } from 'payload'
|
||||
|
||||
import { mergeErrorPaths } from './mergeErrorPaths.js'
|
||||
|
||||
@@ -34,7 +33,6 @@ export const mergeServerFormState = ({
|
||||
'valid',
|
||||
'errorMessage',
|
||||
'errorPaths',
|
||||
'rows',
|
||||
'customComponents',
|
||||
'requiresRender',
|
||||
]
|
||||
@@ -77,6 +75,26 @@ export const mergeServerFormState = ({
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to intelligently merge the rows array to ensure no rows are lost or added while the request was pending
|
||||
* For example, the server response could come back with a row which has been deleted on the client
|
||||
* Loop over the incoming rows, if it exists in client side form state, merge in any new properties from the server
|
||||
*/
|
||||
if (Array.isArray(incomingState[path].rows)) {
|
||||
incomingState[path].rows.forEach((row) => {
|
||||
const matchedExistingRowIndex = newFieldState.rows.findIndex(
|
||||
(existingRow) => existingRow.id === row.id,
|
||||
)
|
||||
|
||||
if (matchedExistingRowIndex > -1) {
|
||||
newFieldState.rows[matchedExistingRowIndex] = {
|
||||
...newFieldState.rows[matchedExistingRowIndex],
|
||||
...row,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle adding all the remaining props that should be updated in the local form state from the server form state
|
||||
*/
|
||||
|
||||
@@ -316,6 +316,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
|
||||
acc.rows.push({
|
||||
id: row.id,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
const previousRows = previousFormState?.[path]?.rows || []
|
||||
@@ -495,6 +496,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
acc.rowMetadata.push({
|
||||
id: row.id,
|
||||
blockType: row.blockType,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
const collapsedRowIDs = preferences?.fields?.[path]?.collapsed
|
||||
|
||||
Reference in New Issue
Block a user