Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5ec158c
fix: improve merging with batched objects (nullish values ignored pre…
Sep 12, 2023
71c528e
fix: fix fastMerge not being able to merge nullish values
Sep 12, 2023
bd29273
fix: improve and simplify batched object merging
Sep 12, 2023
a9c0463
fix: remove unused util function
Sep 12, 2023
39c7784
feat: add new test which tests merging objects and undefined in a batch
Sep 13, 2023
e565266
fix: ignore undefined values in a batch of object changes
Sep 13, 2023
9460769
feat: bump the peer dependency version of react-native-performance
allroundexperts Sep 14, 2023
050178d
1.0.77
OSBotify Sep 14, 2023
5c04af8
fix: improve comment
Sep 14, 2023
d264789
fix: add comment about fastMerge change
Sep 14, 2023
5ee0111
Merge branch 'main' into @chrispader/improve-merge-object-batching
Sep 19, 2023
36e10a2
fix: remove console.log
Sep 19, 2023
7e52d6d
Update lib/Onyx.js
chrispader Sep 20, 2023
40ee983
Testing dependent keys
tgolen Sep 20, 2023
f8eec03
Pass withOnyx state to key functions
tgolen Sep 20, 2023
1f5c9b4
Remove unused import
tgolen Sep 20, 2023
1d5b945
Filter out unrelated data before passing state
tgolen Sep 20, 2023
ab06288
Add tests for nested dependencies and filter the state before passing
tgolen Sep 21, 2023
0b2d29b
DRY up method
tgolen Sep 21, 2023
fc75063
1.0.85
OSBotify Sep 21, 2023
54ae205
fix: don't ignore null values
Sep 21, 2023
7bb4657
fix: remove unneccesary comment
Sep 21, 2023
5046349
fix: update comment
Sep 21, 2023
d8b0573
fix: remove fastMerge.js and update comment
Sep 21, 2023
45ecede
fix: update version
Sep 21, 2023
37f0423
Merge branch 'main' into @chrispader/improve-merge-object-batching
Sep 21, 2023
589b182
feat: add additional test
Sep 21, 2023
d3868ea
fix: update variable name
Sep 21, 2023
813dc41
fix: null value not overwriting existing value
Sep 21, 2023
0122acf
fix: move overwriteExistingValue above merge queue clearing
Sep 21, 2023
d8a4fc6
update test
Sep 21, 2023
dcf9862
fix: improve null batching
Sep 22, 2023
0086b91
fix: ignore undefined values in the first place
Sep 22, 2023
cb6d4cc
fix: update onyx test
Sep 22, 2023
1d8ee34
fix: remove unused import
Sep 22, 2023
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
25 changes: 19 additions & 6 deletions lib/Onyx.js
Original file line number Diff line number Diff line change
Expand Up @@ -1082,14 +1082,12 @@ function multiSet(data) {
function applyMerge(existingValue, changes) {
const lastChange = _.last(changes);

if (_.isArray(existingValue) || _.isArray(lastChange)) {
Comment thread
chrispader marked this conversation as resolved.
if (_.isArray(lastChange)) {
return lastChange;
}

if (_.isObject(existingValue) || _.every(changes, _.isObject)) {
// Object values are merged one after the other
// lodash adds a small overhead so we don't use it here
// eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
if (_.some(changes, _.isObject)) {
// Object values are then merged one after the other
return _.reduce(changes, (modifiedData, change) => utils.fastMerge(modifiedData, change),
existingValue || {});
}
Expand Down Expand Up @@ -1120,6 +1118,12 @@ function applyMerge(existingValue, changes) {
* @returns {Promise}
*/
function merge(key, changes) {
// Top-level undefined values are ignored
// Therefore we need to prevent adding them to the merge queue
if (_.isUndefined(changes)) {
return mergeQueue[key] ? mergeQueuePromise[key] : Promise.resolve();
}

// Merge attempts are batched together. The delta should be applied after a single call to get() to prevent a race condition.
// Using the initial value from storage in subsequent merge attempts will lead to an incorrect final merged value.
if (mergeQueue[key]) {
Expand All @@ -1134,12 +1138,21 @@ function merge(key, changes) {
// We first only merge the changes, so we can provide these to the native implementation (SQLite uses only delta changes in "JSON_PATCH" to merge)
let batchedChanges = applyMerge(undefined, mergeQueue[key]);

if (_.isNull(batchedChanges)) {
return remove(key);
}

// The presence of a `null` in the merge queue instructs us to drop the existing value.
// In this case, we can't simply merge the batched changes with the existing value, because then the null in the merge queue would have no effect
Comment thread
chrispader marked this conversation as resolved.
const shouldOverwriteExistingValue = _.includes(mergeQueue[key], null);

// Clean up the write queue, so we don't apply these changes again
delete mergeQueue[key];
delete mergeQueuePromise[key];

// After that we merge the batched changes with the existing value
const modifiedData = utils.removeNullObjectValues(applyMerge(existingValue, [batchedChanges]));
const updatedValue = shouldOverwriteExistingValue ? batchedChanges : applyMerge(existingValue, [batchedChanges]);
const modifiedData = utils.removeNullObjectValues(updatedValue);

// On native platforms we use SQLite which utilises JSON_PATCH to merge changes.
// JSON_PATCH generally removes top-level nullish values from the stored object.
Expand Down
65 changes: 0 additions & 65 deletions lib/fastMerge.js

This file was deleted.

5 changes: 3 additions & 2 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ function mergeObject(target, source) {
* @returns {Object|Array}
*/
function fastMerge(target, source) {
// lodash adds a small overhead so we don't use it here
// eslint-disable-next-line rulesdir/prefer-underscore-method
// We have to ignore arrays and nullish values here,
// otherwise "mergeObject" will throw an error,
// because it expects an object as "source"
if (_.isArray(source) || _.isNull(source) || _.isUndefined(source)) {
return source;
}
Expand Down
59 changes: 57 additions & 2 deletions tests/unit/onyxTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ describe('Onyx', () => {
});
});

it('should merge an object with undefined', () => {
it('should ignore top-level undefined values', () => {
let testKeyValue;

connectionID = Onyx.connect({
Expand All @@ -219,7 +219,7 @@ describe('Onyx', () => {
return Onyx.merge(ONYX_KEYS.TEST_KEY, undefined);
})
.then(() => {
expect(testKeyValue).toEqual(undefined);
expect(testKeyValue).toEqual({test1: 'test1'});
});
});

Expand Down Expand Up @@ -848,4 +848,59 @@ describe('Onyx', () => {
Onyx.disconnect(connectionIDs);
});
});

it('should merge an object with a batch of objects and undefined', () => {
let testKeyValue;

connectionID = Onyx.connect({
key: ONYX_KEYS.TEST_KEY,
initWithStoredValues: false,
callback: (value) => {
testKeyValue = value;
},
});

return Onyx.set(ONYX_KEYS.TEST_KEY, {test1: 'test1'})
.then(() => {
expect(testKeyValue).toEqual({test1: 'test1'});
Onyx.merge(ONYX_KEYS.TEST_KEY, {test2: 'test2'});
Onyx.merge(ONYX_KEYS.TEST_KEY, {test3: 'test3'});
Onyx.merge(ONYX_KEYS.TEST_KEY, undefined);
Onyx.merge(ONYX_KEYS.TEST_KEY, {test4: 'test4'});
Onyx.merge(ONYX_KEYS.TEST_KEY, undefined);
return waitForPromisesToResolve();
})
.then(() => {
expect(testKeyValue).toEqual({
test1: 'test1', test2: 'test2', test3: 'test3', test4: 'test4',
});
});
});

it('should merge an object with null and overwrite the value', () => {
let testKeyValue;

connectionID = Onyx.connect({
key: ONYX_KEYS.TEST_KEY,
initWithStoredValues: false,
callback: (value) => {
testKeyValue = value;
},
});

return Onyx.set(ONYX_KEYS.TEST_KEY, {test1: 'test1'})
.then(() => {
expect(testKeyValue).toEqual({test1: 'test1'});
Onyx.merge(ONYX_KEYS.TEST_KEY, null);
Onyx.merge(ONYX_KEYS.TEST_KEY, {test2: 'test2'});
Comment thread
amyevans marked this conversation as resolved.
Onyx.merge(ONYX_KEYS.TEST_KEY, {test3: 'test3'});
return waitForPromisesToResolve();
})
.then(() => {
expect(testKeyValue).toEqual({
test2: 'test2',
test3: 'test3',
});
});
});
});