diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 0bc422d639bc..98688c91fc03 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. @@ -33,6 +33,9 @@ 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. 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 // apply successData or failureData. This ensures that we do not update any pending, loading, or other UI states contained // in successData/failureData until after the component has received and API data. @@ -41,7 +44,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 +55,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..fe433aaf33da 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 queues optimistic onyx updates to be applied later when flushing the queue + */ +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,20 @@ 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(() => Onyx.update(queuedOnyxOptimisticUpdates)) + .then(() => { + queuedOnyxUpdates = []; + queuedOnyxOptimisticUpdates = []; + return Promise.resolve(); + }); } function isEmpty() { return queuedOnyxUpdates.length === 0; } -export {queueOnyxUpdates, flushQueue, isEmpty}; +export {queueOnyxUpdates, flushQueue, isEmpty, queueOnyxOptimisticUpdates};