Skip to content

Commit 5d611d6

Browse files
dhasilvavianasw
andauthored
Forms: Add notice after optimistic response status update (#46056)
* optimizations from review * add notice to pending action and update load state logic * changelog * fix js tests * fix issue with wrong page loaded on undo * fix premature removal of pending actions * Fix UI flicker when undoing status change actions Fixes a race condition where clicking undo immediately after marking a response as spam/trash would cause the UI to flicker. The item would briefly disappear and reappear, and checkboxes would flash on all entries. Root cause: When undo was clicked, the original action's API response would arrive after the undo's optimistic update, overwriting it with stale data. This created a sequence where the UI would update to the wrong state (spam) before correcting itself (publish). Solution: Make undo wait for the original action's refetch to complete before starting. Implemented by: - Tracking pending refetch promises in a Map by actionId - Making undo onClick async and awaiting the original refetch - Using undoTriggered flag to skip refetch if undo clicked quickly - Removing original pending action state before starting undo This ensures only one refetch result is applied, eliminating the flicker while keeping undo responsive. --------- Co-authored-by: William Viana <vianasw@gmail.com>
1 parent dfe0943 commit 5d611d6

File tree

10 files changed

+658
-287
lines changed

10 files changed

+658
-287
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: changed
3+
4+
Forms: Add notice after optimistic response status update

projects/packages/forms/src/dashboard/hooks/use-inbox-data.ts

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,24 @@ export default function useInboxData(): UseInboxDataReturn {
7878
const urlStatus = searchParams.get( 'status' );
7979
const statusFilter = getStatusFilter( urlStatus );
8080

81-
const { selectedResponsesCount, currentStatus, currentQuery, filterOptions, invalidRecords } =
82-
useSelect(
83-
select => ( {
84-
selectedResponsesCount: select( dashboardStore ).getSelectedResponsesCount(),
85-
currentStatus: select( dashboardStore ).getCurrentStatus(),
86-
currentQuery: select( dashboardStore ).getCurrentQuery(),
87-
filterOptions: select( dashboardStore ).getFilters(),
88-
invalidRecords: select( dashboardStore ).getInvalidRecords(),
89-
} ),
90-
[]
91-
);
81+
const {
82+
selectedResponsesCount,
83+
currentStatus,
84+
currentQuery,
85+
filterOptions,
86+
invalidRecords,
87+
hasPendingActions,
88+
} = useSelect(
89+
select => ( {
90+
selectedResponsesCount: select( dashboardStore ).getSelectedResponsesCount(),
91+
currentStatus: select( dashboardStore ).getCurrentStatus(),
92+
currentQuery: select( dashboardStore ).getCurrentQuery(),
93+
filterOptions: select( dashboardStore ).getFilters(),
94+
invalidRecords: select( dashboardStore ).getInvalidRecords(),
95+
hasPendingActions: select( dashboardStore ).hasPendingActions(),
96+
} ),
97+
[]
98+
);
9299

93100
// Track the frozen invalid_ids for the current page
94101
// This prevents re-fetching when new items are marked as invalid
@@ -146,19 +153,6 @@ export default function useInboxData(): UseInboxDataReturn {
146153
[ rawRecords ]
147154
);
148155

149-
/**
150-
* Helper function to determine the effective status of a record,
151-
* considering optimistic edits (status field in edits).
152-
*
153-
* @param {FormResponse} record - The record to check.
154-
* @return {string} The effective status of the record.
155-
*/
156-
const getEffectiveStatus = ( record: FormResponse ): string => {
157-
// getEditedEntityRecord merges edits with the original record,
158-
// so if status was edited, it will be in the edited record
159-
return record.status || 'publish';
160-
};
161-
162156
/**
163157
* Helper function to check if a status matches the current status filter.
164158
*
@@ -178,9 +172,7 @@ export default function useInboxData(): UseInboxDataReturn {
178172
const records = useMemo( () => {
179173
// Filter records based on their effective status (considering optimistic edits)
180174
const filteredRecords = ( editedRecords || [] ).filter( ( record: FormResponse ) => {
181-
const effectiveStatus = getEffectiveStatus( record );
182-
183-
return statusMatchesFilter( effectiveStatus, statusFilter );
175+
return statusMatchesFilter( record.status, statusFilter );
184176
} );
185177

186178
return filteredRecords.map( record => {
@@ -243,7 +235,13 @@ export default function useInboxData(): UseInboxDataReturn {
243235
[ countsQueryParams ]
244236
);
245237

246-
const isLoadingData = ! rawRecords?.length && ! hasResolved;
238+
// Show loading if:
239+
// 1. No records and query hasn't resolved yet (initial load)
240+
// 2. No filtered records but there are pending actions (optimistic update removed all items from current view)
241+
// Note: We check records.length (filtered) not rawRecords.length because optimistic updates
242+
// change status, so items are filtered out of records but still exist in rawRecords
243+
const isLoadingData =
244+
( ! rawRecords?.length && ! hasResolved ) || ( ! records?.length && hasPendingActions );
247245

248246
return {
249247
totalItemsInbox,

0 commit comments

Comments
 (0)