Skip to content

Commit 201ae7e

Browse files
authored
Merge pull request #600 from callstack-internal/zirgulis/add-setCollection-to-update-method
add setCollection support to Onyx.update()
2 parents cf3ee8d + 738201f commit 201ae7e

File tree

4 files changed

+157
-55
lines changed

4 files changed

+157
-55
lines changed

lib/Onyx.ts

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type {
2626
OnyxUpdate,
2727
OnyxValue,
2828
OnyxInput,
29+
OnyxMethodMap,
2930
} from './types';
3031
import OnyxUtils from './OnyxUtils';
3132
import logMessages from './logMessages';
@@ -599,7 +600,7 @@ function updateSnapshots(data: OnyxUpdate[]) {
599600
function update(data: OnyxUpdate[]): Promise<void> {
600601
// First, validate the Onyx object is in the format we expect
601602
data.forEach(({onyxMethod, key, value}) => {
602-
if (![OnyxUtils.METHOD.CLEAR, OnyxUtils.METHOD.SET, OnyxUtils.METHOD.MERGE, OnyxUtils.METHOD.MERGE_COLLECTION, OnyxUtils.METHOD.MULTI_SET].includes(onyxMethod)) {
603+
if (!Object.values(OnyxUtils.METHOD).includes(onyxMethod)) {
603604
throw new Error(`Invalid onyxMethod ${onyxMethod} in Onyx update.`);
604605
}
605606
if (onyxMethod === OnyxUtils.METHOD.MULTI_SET) {
@@ -636,18 +637,14 @@ function update(data: OnyxUpdate[]): Promise<void> {
636637
let clearPromise: Promise<void> = Promise.resolve();
637638

638639
data.forEach(({onyxMethod, key, value}) => {
639-
switch (onyxMethod) {
640-
case OnyxUtils.METHOD.SET:
641-
enqueueSetOperation(key, value);
642-
break;
643-
case OnyxUtils.METHOD.MERGE:
644-
enqueueMergeOperation(key, value);
645-
break;
646-
case OnyxUtils.METHOD.MERGE_COLLECTION: {
640+
const handlers: Record<OnyxMethodMap[keyof OnyxMethodMap], (k: typeof key, v: typeof value) => void> = {
641+
[OnyxUtils.METHOD.SET]: enqueueSetOperation,
642+
[OnyxUtils.METHOD.MERGE]: enqueueMergeOperation,
643+
[OnyxUtils.METHOD.MERGE_COLLECTION]: () => {
647644
const collection = value as Collection<CollectionKey, unknown, unknown>;
648645
if (!OnyxUtils.isValidNonEmptyCollectionForMerge(collection)) {
649646
Logger.logInfo('mergeCollection enqueued within update() with invalid or empty value. Skipping this operation.');
650-
break;
647+
return;
651648
}
652649

653650
// Confirm all the collection keys belong to the same parent
@@ -656,18 +653,15 @@ function update(data: OnyxUpdate[]): Promise<void> {
656653
const mergedCollection: OnyxInputKeyValueMapping = collection;
657654
collectionKeys.forEach((collectionKey) => enqueueMergeOperation(collectionKey, mergedCollection[collectionKey]));
658655
}
659-
660-
break;
661-
}
662-
case OnyxUtils.METHOD.MULTI_SET:
663-
Object.entries(value).forEach(([entryKey, entryValue]) => enqueueSetOperation(entryKey, entryValue));
664-
break;
665-
case OnyxUtils.METHOD.CLEAR:
656+
},
657+
[OnyxUtils.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollection(k, v as Collection<CollectionKey, unknown, unknown>)),
658+
[OnyxUtils.METHOD.MULTI_SET]: (k, v) => Object.entries(v as Partial<OnyxInputKeyValueMapping>).forEach(([entryKey, entryValue]) => enqueueSetOperation(entryKey, entryValue)),
659+
[OnyxUtils.METHOD.CLEAR]: () => {
666660
clearPromise = clear();
667-
break;
668-
default:
669-
break;
670-
}
661+
},
662+
};
663+
664+
handlers[onyxMethod](key, value);
671665
});
672666

673667
// Group all the collection-related keys and update each collection in a single `mergeCollection` call.

lib/OnyxUtils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,4 +1418,6 @@ const OnyxUtils = {
14181418
getEvictionBlocklist,
14191419
};
14201420

1421+
export type {OnyxMethod};
1422+
14211423
export default OnyxUtils;

lib/types.ts

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {Merge} from 'type-fest';
22
import type {BuiltIns} from 'type-fest/source/internal';
33
import type OnyxUtils from './OnyxUtils';
44
import type {WithOnyxInstance, WithOnyxState} from './withOnyx/types';
5+
import type {OnyxMethod} from './OnyxUtils';
56

67
/**
78
* Utility type that excludes `null` from the type `TValue`.
@@ -395,41 +396,46 @@ type OnyxMergeInput<TKey extends OnyxKey> = OnyxInput<TKey>;
395396
*/
396397
type OnyxMergeCollectionInput<TKey extends OnyxKey, TMap = object> = Collection<TKey, NonNullable<OnyxInput<TKey>>, TMap>;
397398

399+
type OnyxMethodMap = typeof OnyxUtils.METHOD;
400+
401+
// Maps onyx methods to their corresponding value types
402+
type OnyxMethodValueMap = {
403+
[OnyxUtils.METHOD.SET]: {
404+
key: OnyxKey;
405+
value: OnyxSetInput<OnyxKey>;
406+
};
407+
[OnyxUtils.METHOD.MULTI_SET]: {
408+
key: OnyxKey;
409+
value: OnyxMultiSetInput;
410+
};
411+
[OnyxUtils.METHOD.MERGE]: {
412+
key: OnyxKey;
413+
value: OnyxMergeInput<OnyxKey>;
414+
};
415+
[OnyxUtils.METHOD.CLEAR]: {
416+
key: OnyxKey;
417+
value?: undefined;
418+
};
419+
[OnyxUtils.METHOD.MERGE_COLLECTION]: {
420+
key: CollectionKeyBase;
421+
value: OnyxMergeCollectionInput<CollectionKeyBase>;
422+
};
423+
[OnyxUtils.METHOD.SET_COLLECTION]: {
424+
key: CollectionKeyBase;
425+
value: OnyxMergeCollectionInput<CollectionKeyBase>;
426+
};
427+
};
428+
398429
/**
399-
* Represents different kinds of updates that can be passed to `Onyx.update()` method. It is a discriminated union of
400-
* different update methods (`SET`, `MERGE`, `MERGE_COLLECTION`), each with their own key and value structure.
430+
* OnyxUpdate type includes all onyx methods used in OnyxMethodValueMap.
431+
* If a new method is added to OnyxUtils.METHOD constant, it must be added to OnyxMethodValueMap type.
432+
* Otherwise it will show static type errors.
401433
*/
402-
type OnyxUpdate =
403-
| {
404-
[TKey in OnyxKey]:
405-
| {
406-
onyxMethod: typeof OnyxUtils.METHOD.SET;
407-
key: TKey;
408-
value: OnyxSetInput<TKey>;
409-
}
410-
| {
411-
onyxMethod: typeof OnyxUtils.METHOD.MULTI_SET;
412-
key: TKey;
413-
value: OnyxMultiSetInput;
414-
}
415-
| {
416-
onyxMethod: typeof OnyxUtils.METHOD.MERGE;
417-
key: TKey;
418-
value: OnyxMergeInput<TKey>;
419-
}
420-
| {
421-
onyxMethod: typeof OnyxUtils.METHOD.CLEAR;
422-
key: TKey;
423-
value?: undefined;
424-
};
425-
}[OnyxKey]
426-
| {
427-
[TKey in CollectionKeyBase]: {
428-
onyxMethod: typeof OnyxUtils.METHOD.MERGE_COLLECTION;
429-
key: TKey;
430-
value: OnyxMergeCollectionInput<TKey>;
431-
};
432-
}[CollectionKeyBase];
434+
type OnyxUpdate = {
435+
[Method in OnyxMethod]: {
436+
onyxMethod: Method;
437+
} & OnyxMethodValueMap[Method];
438+
}[OnyxMethod];
433439

434440
/**
435441
* Represents the options used in `Onyx.init()` method.
@@ -507,6 +513,8 @@ export type {
507513
OnyxMultiSetInput,
508514
OnyxMergeInput,
509515
OnyxMergeCollectionInput,
516+
OnyxMethod,
517+
OnyxMethodMap,
510518
OnyxUpdate,
511519
OnyxValue,
512520
Selector,

tests/unit/onyxTest.ts

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1340,7 +1340,7 @@ describe('Onyx', () => {
13401340
return waitForPromisesToResolve();
13411341
})
13421342
.then(() => {
1343-
expect(testKeyValue).toEqual(undefined);
1343+
expect(testKeyValue).toBeUndefined();
13441344
});
13451345
});
13461346

@@ -1807,5 +1807,103 @@ describe('Onyx', () => {
18071807
});
18081808
});
18091809
});
1810+
1811+
it('should properly handle setCollection operations in update()', () => {
1812+
const routeA = `${ONYX_KEYS.COLLECTION.ROUTES}A`;
1813+
const routeB = `${ONYX_KEYS.COLLECTION.ROUTES}B`;
1814+
const routeC = `${ONYX_KEYS.COLLECTION.ROUTES}C`;
1815+
1816+
let routesCollection: unknown;
1817+
1818+
connection = Onyx.connect({
1819+
key: ONYX_KEYS.COLLECTION.ROUTES,
1820+
initWithStoredValues: false,
1821+
callback: (value) => {
1822+
routesCollection = value;
1823+
},
1824+
waitForCollectionCallback: true,
1825+
});
1826+
1827+
return Onyx.mergeCollection(ONYX_KEYS.COLLECTION.ROUTES, {
1828+
[routeA]: {name: 'Route A'},
1829+
[routeB]: {name: 'Route B'},
1830+
[routeC]: {name: 'Route C'},
1831+
} as GenericCollection)
1832+
.then(() => {
1833+
return Onyx.update([
1834+
{
1835+
onyxMethod: Onyx.METHOD.SET_COLLECTION,
1836+
key: ONYX_KEYS.COLLECTION.ROUTES,
1837+
value: {
1838+
[routeA]: {name: 'New Route A'},
1839+
[routeB]: {name: 'New Route B'},
1840+
},
1841+
},
1842+
]);
1843+
})
1844+
.then(() => {
1845+
expect(routesCollection).toEqual({
1846+
[routeA]: {name: 'New Route A'},
1847+
[routeB]: {name: 'New Route B'},
1848+
});
1849+
});
1850+
});
1851+
1852+
it('should handle mixed operations with setCollection in update()', () => {
1853+
const routeA = `${ONYX_KEYS.COLLECTION.ROUTES}A`;
1854+
const routeB = `${ONYX_KEYS.COLLECTION.ROUTES}B`;
1855+
const testKey = ONYX_KEYS.TEST_KEY;
1856+
let routesCollection: unknown;
1857+
1858+
connection = Onyx.connect({
1859+
key: ONYX_KEYS.COLLECTION.ROUTES,
1860+
initWithStoredValues: false,
1861+
callback: (value) => {
1862+
routesCollection = value;
1863+
},
1864+
waitForCollectionCallback: true,
1865+
});
1866+
1867+
let testKeyValue: unknown;
1868+
Onyx.connect({
1869+
key: testKey,
1870+
callback: (value) => {
1871+
testKeyValue = value;
1872+
},
1873+
});
1874+
1875+
return Onyx.mergeCollection(ONYX_KEYS.COLLECTION.ROUTES, {
1876+
[routeA]: {name: 'Route A'},
1877+
[routeB]: {name: 'Route B'},
1878+
} as GenericCollection)
1879+
.then(() => {
1880+
return Onyx.update([
1881+
{
1882+
onyxMethod: Onyx.METHOD.SET,
1883+
key: testKey,
1884+
value: 'test value',
1885+
},
1886+
{
1887+
onyxMethod: Onyx.METHOD.SET_COLLECTION,
1888+
key: ONYX_KEYS.COLLECTION.ROUTES,
1889+
value: {
1890+
[routeA]: {name: 'Final Route A'},
1891+
},
1892+
},
1893+
{
1894+
onyxMethod: Onyx.METHOD.MERGE,
1895+
key: testKey,
1896+
value: 'merged value',
1897+
},
1898+
]);
1899+
})
1900+
.then(() => {
1901+
expect(routesCollection).toEqual({
1902+
[routeA]: {name: 'Final Route A'},
1903+
});
1904+
1905+
expect(testKeyValue).toBe('merged value');
1906+
});
1907+
});
18101908
});
18111909
});

0 commit comments

Comments
 (0)