From 3b4b0836b7dcdf42195d1b1a993b77e07563ed6b Mon Sep 17 00:00:00 2001
From: nkdengineer <nkdengineer@outlook.com>
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<unknown> = request?.data?.apiRequestType === CONST.API_REQUEST_TYPE.WRITE ? queueOnyxUpdates : Onyx.update;
+    const updateOptimisticHandler: (updates: OnyxUpdate[]) => Promise<unknown> = 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<void> {
     return Promise.resolve();
 }
 
+/**
+ * @param updates Onyx updates to queue for later
+ */
+function queueOnyxOptimisticUpdates(updates: OnyxUpdate[]): Promise<void> {
+    queuedOnyxOptimisticUpdates = queuedOnyxOptimisticUpdates.concat(updates);
+
+    return Promise.resolve();
+}
+
 function flushQueue(): Promise<void> {
     if (!currentAccountID && !CONFIG.IS_TEST_ENV && !CONFIG.E2E_TESTING) {
         const preservedKeys: OnyxKey[] = [
@@ -44,15 +54,23 @@ function flushQueue(): Promise<void> {
         ];
 
         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 <nkdengineer@outlook.com>
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<void> {
     }
 
     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 <monilbhavsar25@gmail.com>
---
 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<void> {
 }
 
 /**
- * @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<void> {
     queuedOnyxOptimisticUpdates = queuedOnyxOptimisticUpdates.concat(updates);

From 4ca9e374465d582e0dab7656571d875377b10653 Mon Sep 17 00:00:00 2001
From: nkdengineer <nkdengineer@outlook.com>
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<unknown> = 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<unknown> = 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 <luacmartins@gmail.com>
---
 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<unknown> = 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<unknown> = 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
