Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2f404e6
Remove the current batching mechanism
VickyStash Oct 16, 2025
fec6d6b
Make code more readable
VickyStash Oct 16, 2025
6308802
Have one next macrotask instead of multiple
VickyStash Oct 21, 2025
81b1773
Merge branch 'main' into VickyStash/poc/remove-batching
VickyStash Jan 7, 2026
e93419e
Fix TS error
VickyStash Jan 7, 2026
b0bad98
Improve the logic to schedule the macrotask only when needed
VickyStash Jan 7, 2026
605bc69
Re-run checks
VickyStash Jan 7, 2026
4e3e956
Re-run reassure check
VickyStash Jan 7, 2026
e423194
Fix E/App tests
VickyStash Jan 8, 2026
fd10fea
Fix the test of E/App tests
VickyStash Jan 8, 2026
c3c2f0b
Merge branch 'main' into VickyStash/poc/remove-batching
VickyStash Jan 12, 2026
798ac42
Re-run reassure test
VickyStash Jan 12, 2026
19fa645
Update API docs
VickyStash Jan 12, 2026
35e880f
Merge branch 'main' into VickyStash/poc/remove-batching
VickyStash Jan 13, 2026
606bd64
Test change
VickyStash Jan 13, 2026
b30678f
Merge branch 'VickyStash/poc/remove-batching' into VickyStash/debug-r…
VickyStash Jan 13, 2026
e8cc46d
Experiment 1
VickyStash Jan 13, 2026
5069c1d
Revert "Experiment 1"
VickyStash Jan 13, 2026
79d7400
Revert "Merge pull request #720 from margelo/@chrispader/fix-collecti…
VickyStash Jan 13, 2026
7ed1af8
Reapply "Merge pull request #720 from margelo/@chrispader/fix-collect…
VickyStash Jan 13, 2026
c9a1c43
Experiment 2
VickyStash Jan 13, 2026
8227551
Experiment 3
VickyStash Jan 13, 2026
acb09d3
Experiment 4
VickyStash Jan 13, 2026
cd5e14c
Experiment 5
VickyStash Jan 13, 2026
25eca79
Experiment 6
VickyStash Jan 13, 2026
a7855a6
Experiment 7
VickyStash Jan 13, 2026
67af56f
Experiment 8
VickyStash Jan 13, 2026
1c19266
Experiment 9
VickyStash Jan 13, 2026
4ce13d7
Return back
VickyStash Jan 13, 2026
3f35861
Experiment 10
VickyStash Jan 14, 2026
9462313
Experiment 11
VickyStash Jan 14, 2026
f2b4fec
Experiment 12
VickyStash Jan 14, 2026
1db12e5
Experiment 13
VickyStash Jan 14, 2026
e4b6b94
Experiment 14
VickyStash Jan 14, 2026
bc3ff24
Update reassure test
VickyStash Jan 14, 2026
553c4fd
Experiment 15
VickyStash Jan 14, 2026
b8adc1f
Revert "Update reassure test"
VickyStash Jan 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 31 additions & 17 deletions API-INTERNAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@
<dt><a href="#initStoreValues">initStoreValues(keys, initialKeyStates, evictableKeys)</a></dt>
<dd><p>Sets the initial values for the Onyx store</p>
</dd>
<dt><a href="#maybeFlushBatchUpdates">maybeFlushBatchUpdates()</a></dt>
<dd><p>We are batching together onyx updates. This helps with use cases where we schedule onyx updates after each other.
This happens for example in the Onyx.update function, where we process API responses that might contain a lot of
update operations. Instead of calling the subscribers for each update operation, we batch them together which will
cause react to schedule the updates at once instead of after each other. This is mainly a performance optimization.</p>
</dd>
<dt><a href="#reduceCollectionWithSelector">reduceCollectionWithSelector()</a></dt>
<dd><p>Takes a collection of items (eg. {testKey_1:{a:&#39;a&#39;}, testKey_2:{b:&#39;b&#39;}})
and runs it through a reducer function to return a subset of the data according to a selector.
Expand Down Expand Up @@ -61,6 +55,9 @@ to the values for those keys (correctly typed) such as <code>[OnyxCollection&lt;
<dd><p>Checks to see if the subscriber&#39;s supplied key
is associated with a collection of keys.</p>
</dd>
<dt><a href="#isCollectionMember">isCollectionMember(key)</a> ⇒</dt>
<dd><p>Checks if a given key is a collection member key (not just a collection key).</p>
</dd>
<dt><a href="#splitCollectionMemberKey">splitCollectionMemberKey(key, collectionKey)</a> ⇒</dt>
<dd><p>Splits a collection member key into the collection key part and the ID part.</p>
</dd>
Expand Down Expand Up @@ -98,11 +95,14 @@ run out of storage the least recently accessed key can be removed.</p>
<dt><a href="#getCollectionDataAndSendAsObject">getCollectionDataAndSendAsObject()</a></dt>
<dd><p>Gets the data for a given an array of matching keys, combines them into an object, and sends the result back to the subscriber.</p>
</dd>
<dt><a href="#prepareSubscriberUpdate">prepareSubscriberUpdate(callback)</a></dt>
<dd><p>Delays promise resolution until the next macrotask to prevent race condition if the key subscription is in progress.</p>
</dd>
<dt><a href="#scheduleSubscriberUpdate">scheduleSubscriberUpdate()</a></dt>
<dd><p>Schedules an update that will be appended to the macro task queue (so it doesn&#39;t update the subscribers immediately).</p>
</dd>
<dt><a href="#scheduleNotifyCollectionSubscribers">scheduleNotifyCollectionSubscribers()</a></dt>
<dd><p>This method is similar to notifySubscribersOnNextTick but it is built for working specifically with collections
<dd><p>This method is similar to scheduleSubscriberUpdate but it is built for working specifically with collections
so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
subscriber callbacks receive the data in a different format than they normally expect and it breaks code.</p>
</dd>
Expand Down Expand Up @@ -230,15 +230,6 @@ Sets the initial values for the Onyx store
| initialKeyStates | initial data to set when `init()` and `clear()` are called |
| evictableKeys | This is an array of keys (individual or collection patterns) that when provided to Onyx are flagged as "safe" for removal. |

<a name="maybeFlushBatchUpdates"></a>

## maybeFlushBatchUpdates()
We are batching together onyx updates. This helps with use cases where we schedule onyx updates after each other.
This happens for example in the Onyx.update function, where we process API responses that might contain a lot of
update operations. Instead of calling the subscribers for each update operation, we batch them together which will
cause react to schedule the updates at once instead of after each other. This is mainly a performance optimization.

**Kind**: global function
<a name="reduceCollectionWithSelector"></a>

## reduceCollectionWithSelector()
Expand Down Expand Up @@ -304,6 +295,18 @@ Checks to see if the subscriber's supplied key
is associated with a collection of keys.

**Kind**: global function
<a name="isCollectionMember"></a>

## isCollectionMember(key) ⇒
Checks if a given key is a collection member key (not just a collection key).

**Kind**: global function
**Returns**: true if the key is a collection member, false otherwise

| Param | Description |
| --- | --- |
| key | The key to check |

<a name="splitCollectionMemberKey"></a>

## splitCollectionMemberKey(key, collectionKey) ⇒
Expand Down Expand Up @@ -402,6 +405,17 @@ run out of storage the least recently accessed key can be removed.
Gets the data for a given an array of matching keys, combines them into an object, and sends the result back to the subscriber.

**Kind**: global function
<a name="prepareSubscriberUpdate"></a>

## prepareSubscriberUpdate(callback)
Delays promise resolution until the next macrotask to prevent race condition if the key subscription is in progress.

**Kind**: global function

| Param | Description |
| --- | --- |
| callback | The keyChanged/keysChanged callback |

<a name="scheduleSubscriberUpdate"></a>

## scheduleSubscriberUpdate()
Expand All @@ -415,7 +429,7 @@ scheduleSubscriberUpdate(key, value, subscriber => subscriber.initWithStoredValu
<a name="scheduleNotifyCollectionSubscribers"></a>

## scheduleNotifyCollectionSubscribers()
This method is similar to notifySubscribersOnNextTick but it is built for working specifically with collections
This method is similar to scheduleSubscriberUpdate but it is built for working specifically with collections
so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
subscriber callbacks receive the data in a different format than they normally expect and it breaks code.

Expand Down
2 changes: 1 addition & 1 deletion API.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ applied in the order they were called. Note: `Onyx.set()` calls do not work this
**Example**
```js
Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Joe']); // -> ['Joe']
Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Jack']); // -> ['Jack']
Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Jack']); // -> ['Joe', 'Jack']
Onyx.merge(ONYXKEYS.POLICY, {id: 1}); // -> {id: 1}
Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'}
```
Expand Down
3 changes: 0 additions & 3 deletions jestSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,3 @@ jest.mock('react-native-nitro-sqlite', () => ({
}));

jest.useRealTimers();

const unstable_batchedUpdates_jest = require('react-test-renderer').unstable_batchedUpdates;
require('./lib/batch.native').default = unstable_batchedUpdates_jest;
2 changes: 2 additions & 0 deletions lib/Onyx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import * as GlobalSettings from './GlobalSettings';
import decorateWithMetrics from './metrics';
import OnyxMerge from './OnyxMerge';

/** Test change */

/** Initialize the store with actions and listening for storage events */
function init({
keys = {},
Expand Down
23 changes: 14 additions & 9 deletions lib/OnyxUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import * as Logger from './Logger';
import type Onyx from './Onyx';
import cache, {TASK} from './OnyxCache';
import * as Str from './Str';
import unstable_batchedUpdates from './batch';
import Storage from './storage';
import type {
CollectionKey,
Expand Down Expand Up @@ -89,6 +88,7 @@ let onyxKeyToSubscriptionIDs = new Map();
let defaultKeyStates: Record<OnyxKey, OnyxValue<OnyxKey>> = {};

let batchUpdatesPromise: Promise<void> | null = null;
let batchUpdatesTimeoutID: number | null = null;
let batchUpdatesQueue: Array<() => void> = [];

// Used for comparison with a new update to avoid invoking the Onyx.connect callback with the same data.
Expand Down Expand Up @@ -228,15 +228,15 @@ function maybeFlushBatchUpdates(): Promise<void> {
* We may investigate if (setTimeout, 1) (which in React Native is equal to requestAnimationFrame) works even better
* then the batch will be flushed on next frame.
*/
setTimeout(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
batchUpdatesTimeoutID = setTimeout(() => {
const updatesCopy = batchUpdatesQueue;
batchUpdatesQueue = [];
batchUpdatesPromise = null;
unstable_batchedUpdates(() => {
for (const applyUpdates of updatesCopy) {
applyUpdates();
}
});
for (const applyUpdates of updatesCopy) {
applyUpdates();
}

resolve();
}, 0);
Expand Down Expand Up @@ -863,7 +863,7 @@ function scheduleSubscriberUpdate<TKey extends OnyxKey>(
isProcessingCollectionUpdate = false,
): Promise<void> {
const promise = Promise.resolve().then(() => keyChanged(key, value, canUpdateSubscriber, true, isProcessingCollectionUpdate));
batchUpdates(() => keyChanged(key, value, canUpdateSubscriber, false, isProcessingCollectionUpdate));
// batchUpdates(() => keyChanged(key, value, canUpdateSubscriber, false, isProcessingCollectionUpdate));
return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
}

Expand All @@ -878,7 +878,7 @@ function scheduleNotifyCollectionSubscribers<TKey extends OnyxKey>(
previousValue?: OnyxCollection<KeyValueMapping[TKey]>,
): Promise<void> {
const promise = Promise.resolve().then(() => keysChanged(key, value, previousValue, true));
batchUpdates(() => keysChanged(key, value, previousValue, false));
// batchUpdates(() => keysChanged(key, value, previousValue, false));
return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
}

Expand Down Expand Up @@ -1693,7 +1693,12 @@ function clearOnyxUtilsInternals() {
callbackToStateMapping = {};
onyxKeyToSubscriptionIDs = new Map();
batchUpdatesQueue = [];
batchUpdatesPromise = null;
lastConnectionCallbackData = new Map();
if (batchUpdatesTimeoutID) {
clearTimeout(batchUpdatesTimeoutID);
batchUpdatesTimeoutID = null;
}
}

const OnyxUtils = {
Expand Down
3 changes: 0 additions & 3 deletions lib/batch.native.ts

This file was deleted.

3 changes: 0 additions & 3 deletions lib/batch.ts

This file was deleted.

3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
"@types/lodash": "^4.14.202",
"@types/node": "^20.11.5",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.18",
"@types/react-native": "^0.70.0",
"@types/underscore": "^1.11.15",
"@typescript-eslint/eslint-plugin": "^8.51.0",
Expand All @@ -89,7 +88,6 @@
"prettier": "^2.8.8",
"prop-types": "^15.7.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.76.3",
"react-native-device-info": "^10.3.0",
"react-native-nitro-modules": "^0.27.2",
Expand All @@ -104,7 +102,6 @@
"peerDependencies": {
"idb-keyval": "^6.2.1",
"react": ">=18.1.0",
"react-dom": ">=18.1.0",
"react-native": ">=0.75.0",
"react-native-device-info": "^10.3.0",
"react-native-nitro-modules": ">=0.27.2",
Expand Down
7 changes: 0 additions & 7 deletions tests/perf-test/OnyxUtils.perf-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,6 @@ describe('OnyxUtils', () => {
});
});

describe('batchUpdates / maybeFlushBatchUpdates', () => {
test('one call with 1k updates', async () => {
const updates: Array<() => void> = Array.from({length: 1000}, () => jest.fn);
await measureAsyncFunction(() => Promise.all(updates.map((update) => OnyxUtils.batchUpdates(update))));
});
});

describe('get', () => {
test('10k calls with heavy objects', async () => {
await measureAsyncFunction(() => Promise.all(mockedReportActionsKeys.map((key) => OnyxUtils.get(key))), {
Expand Down
1 change: 0 additions & 1 deletion tests/unit/onyxUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,6 @@ describe('OnyxUtils', () => {
ONYXKEYS.COLLECTION.TEST_KEY,
{[entryKey]: updatedEntryData}, // new collection
initialCollection, // previous collection
true, // notify connect subscribers
);

// Should be called again because data changed
Expand Down
Loading