From 3b4b0836b7dcdf42195d1b1a993b77e07563ed6b Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 20 Mar 2025 15:51:06 +0700 Subject: [PATCH 1/5] update optimistic data after the API response --- src/libs/actions/OnyxUpdates.ts | 9 +++++---- src/libs/actions/QueuedOnyxUpdates.ts | 26 ++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 0bc422d639bc..e5e197ea5c9e 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -10,7 +10,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {OnyxUpdateEvent, OnyxUpdatesFromServer, Request} from '@src/types/onyx'; import type Response from '@src/types/onyx/Response'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import {queueOnyxUpdates} from './QueuedOnyxUpdates'; +import {queueOnyxOptimisticUpdates, queueOnyxUpdates} from './QueuedOnyxUpdates'; // This key needs to be separate from ONYXKEYS.ONYX_UPDATES_FROM_SERVER so that it can be updated without triggering the callback when the server IDs are updated. If that // callback were triggered it would lead to duplicate processing of server updates. @@ -32,6 +32,7 @@ function applyHTTPSOnyxUpdates(request: Request, response: Response) { // For most requests we can immediately update Onyx. For write requests we queue the updates and apply them after the sequential queue has flushed to prevent a replay effect in // the UI. See https://github.com/Expensify/App/issues/12775 for more info. const updateHandler: (updates: OnyxUpdate[]) => Promise = request?.data?.apiRequestType === CONST.API_REQUEST_TYPE.WRITE ? queueOnyxUpdates : Onyx.update; + const updateOptimisticHandler: (updates: OnyxUpdate[]) => Promise = request?.data?.apiRequestType === CONST.API_REQUEST_TYPE.WRITE ? queueOnyxOptimisticUpdates : Onyx.update; // First apply any onyx data updates that are being sent back from the API. We wait for this to complete and then // apply successData or failureData. This ensures that we do not update any pending, loading, or other UI states contained @@ -41,7 +42,7 @@ function applyHTTPSOnyxUpdates(request: Request, response: Response) { .then(() => { // Handle the request's success/failure data (client-side data) if (response.jsonCode === 200 && request.successData) { - return updateHandler(request.successData); + return updateOptimisticHandler(request.successData); } if (response.jsonCode !== 200 && request.failureData) { // 460 jsonCode in Expensify world means "admin required". @@ -52,13 +53,13 @@ function applyHTTPSOnyxUpdates(request: Request, response: Response) { Log.info('[OnyxUpdateManager] Received 460 status code, not applying failure data'); return Promise.resolve(); } - return updateHandler(request.failureData); + return updateOptimisticHandler(request.failureData); } return Promise.resolve(); }) .then(() => { if (request.finallyData) { - return updateHandler(request.finallyData); + return updateOptimisticHandler(request.finallyData); } return Promise.resolve(); }) diff --git a/src/libs/actions/QueuedOnyxUpdates.ts b/src/libs/actions/QueuedOnyxUpdates.ts index 76709c0152f3..ffe82d6ecbed 100644 --- a/src/libs/actions/QueuedOnyxUpdates.ts +++ b/src/libs/actions/QueuedOnyxUpdates.ts @@ -6,6 +6,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; // In this file we manage a queue of Onyx updates while the SequentialQueue is processing. There are functions to get the updates and clear the queue after saving the updates in Onyx. let queuedOnyxUpdates: OnyxUpdate[] = []; +let queuedOnyxOptimisticUpdates: OnyxUpdate[] = []; let currentAccountID: number | undefined; Onyx.connect({ @@ -24,6 +25,15 @@ function queueOnyxUpdates(updates: OnyxUpdate[]): Promise { return Promise.resolve(); } +/** + * @param updates Onyx updates to queue for later + */ +function queueOnyxOptimisticUpdates(updates: OnyxUpdate[]): Promise { + queuedOnyxOptimisticUpdates = queuedOnyxOptimisticUpdates.concat(updates); + + return Promise.resolve(); +} + function flushQueue(): Promise { if (!currentAccountID && !CONFIG.IS_TEST_ENV && !CONFIG.E2E_TESTING) { const preservedKeys: OnyxKey[] = [ @@ -44,15 +54,23 @@ function flushQueue(): Promise { ]; queuedOnyxUpdates = queuedOnyxUpdates.filter((update) => preservedKeys.includes(update.key as OnyxKey)); + queuedOnyxOptimisticUpdates = queuedOnyxOptimisticUpdates.filter((update) => preservedKeys.includes(update.key as OnyxKey)); } - return Onyx.update(queuedOnyxUpdates).then(() => { - queuedOnyxUpdates = []; - }); + return Onyx.update(queuedOnyxUpdates) + .then(() => { + queuedOnyxUpdates = []; + return Promise.resolve(); + }) + .then(() => Onyx.update(queuedOnyxOptimisticUpdates)) + .then(() => { + queuedOnyxOptimisticUpdates = []; + return Promise.resolve(); + }); } function isEmpty() { return queuedOnyxUpdates.length === 0; } -export {queueOnyxUpdates, flushQueue, isEmpty}; +export {queueOnyxUpdates, flushQueue, isEmpty, queueOnyxOptimisticUpdates}; From 75c64231157b2c3c7d3a1672821f154ff89fcb18 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 24 Mar 2025 14:01:42 +0700 Subject: [PATCH 2/5] update logic --- src/libs/actions/QueuedOnyxUpdates.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libs/actions/QueuedOnyxUpdates.ts b/src/libs/actions/QueuedOnyxUpdates.ts index ffe82d6ecbed..21f0397f4329 100644 --- a/src/libs/actions/QueuedOnyxUpdates.ts +++ b/src/libs/actions/QueuedOnyxUpdates.ts @@ -58,12 +58,9 @@ function flushQueue(): Promise { } return Onyx.update(queuedOnyxUpdates) - .then(() => { - queuedOnyxUpdates = []; - return Promise.resolve(); - }) .then(() => Onyx.update(queuedOnyxOptimisticUpdates)) .then(() => { + queuedOnyxUpdates = []; queuedOnyxOptimisticUpdates = []; return Promise.resolve(); }); From cd23bbb658c72e7dc6f5e8016cebe82da99af38b Mon Sep 17 00:00:00 2001 From: nkdengineer <161821005+nkdengineer@users.noreply.github.com> Date: Fri, 28 Mar 2025 01:32:03 +0700 Subject: [PATCH 3/5] Update src/libs/actions/QueuedOnyxUpdates.ts Co-authored-by: Monil Bhavsar --- src/libs/actions/QueuedOnyxUpdates.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/QueuedOnyxUpdates.ts b/src/libs/actions/QueuedOnyxUpdates.ts index 21f0397f4329..fe433aaf33da 100644 --- a/src/libs/actions/QueuedOnyxUpdates.ts +++ b/src/libs/actions/QueuedOnyxUpdates.ts @@ -26,7 +26,7 @@ function queueOnyxUpdates(updates: OnyxUpdate[]): Promise { } /** - * @param updates Onyx updates to queue for later + * @param queues optimistic onyx updates to be applied later when flushing the queue */ function queueOnyxOptimisticUpdates(updates: OnyxUpdate[]): Promise { queuedOnyxOptimisticUpdates = queuedOnyxOptimisticUpdates.concat(updates); From 4ca9e374465d582e0dab7656571d875377b10653 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 28 Mar 2025 01:39:16 +0700 Subject: [PATCH 4/5] add explain comment --- src/libs/actions/OnyxUpdates.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index e5e197ea5c9e..2cf2f1499572 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -32,6 +32,8 @@ function applyHTTPSOnyxUpdates(request: Request, response: Response) { // For most requests we can immediately update Onyx. For write requests we queue the updates and apply them after the sequential queue has flushed to prevent a replay effect in // the UI. See https://github.com/Expensify/App/issues/12775 for more info. const updateHandler: (updates: OnyxUpdate[]) => Promise = request?.data?.apiRequestType === CONST.API_REQUEST_TYPE.WRITE ? queueOnyxUpdates : Onyx.update; + + // Push the front-end data like optimisticData, successData, failureData, finallyData of write requests to another queue to apply them after the responses are updated in onyx const updateOptimisticHandler: (updates: OnyxUpdate[]) => Promise = request?.data?.apiRequestType === CONST.API_REQUEST_TYPE.WRITE ? queueOnyxOptimisticUpdates : Onyx.update; // First apply any onyx data updates that are being sent back from the API. We wait for this to complete and then From af6b31230278fe18e62cbcfe32881fa735fef643 Mon Sep 17 00:00:00 2001 From: nkdengineer <161821005+nkdengineer@users.noreply.github.com> Date: Fri, 28 Mar 2025 02:32:37 +0700 Subject: [PATCH 5/5] Update src/libs/actions/OnyxUpdates.ts Co-authored-by: Carlos Martins --- src/libs/actions/OnyxUpdates.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 2cf2f1499572..98688c91fc03 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -33,7 +33,7 @@ function applyHTTPSOnyxUpdates(request: Request, response: Response) { // the UI. See https://github.com/Expensify/App/issues/12775 for more info. const updateHandler: (updates: OnyxUpdate[]) => Promise = request?.data?.apiRequestType === CONST.API_REQUEST_TYPE.WRITE ? queueOnyxUpdates : Onyx.update; - // Push the front-end data like optimisticData, successData, failureData, finallyData of write requests to another queue to apply them after the responses are updated in onyx + // Push the front-end data like optimisticData, successData, failureData, finallyData of write requests to another queue to apply them after the responses are updated in onyx. This ensures that data like `isLoading` is only applied after the API response is written to local storage. const updateOptimisticHandler: (updates: OnyxUpdate[]) => Promise = request?.data?.apiRequestType === CONST.API_REQUEST_TYPE.WRITE ? queueOnyxOptimisticUpdates : Onyx.update; // First apply any onyx data updates that are being sent back from the API. We wait for this to complete and then