react-native-onyx 1.0.95 → 1.0.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web.development.js +46 -53
- package/dist/web.development.js.map +1 -1
- package/dist/web.min.js +1 -1
- package/dist/web.min.js.map +1 -1
- package/lib/Onyx.d.ts +9 -5
- package/lib/Onyx.js +9 -18
- package/lib/storage/providers/IDBKeyVal.js +1 -1
- package/lib/types.d.ts +19 -38
- package/lib/utils.js +28 -32
- package/lib/withOnyx.js +7 -1
- package/package.json +1 -1
package/lib/Onyx.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {Component} from 'react';
|
|
2
|
+
import {PartialDeep} from 'type-fest';
|
|
2
3
|
import * as Logger from './Logger';
|
|
3
|
-
import {CollectionKey, CollectionKeyBase, DeepRecord, KeyValueMapping,
|
|
4
|
+
import {CollectionKey, CollectionKeyBase, DeepRecord, KeyValueMapping, OnyxCollection, OnyxEntry, OnyxKey, NullableProperties} from './types';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Represents a mapping object where each `OnyxKey` maps to either a value of its corresponding type in `KeyValueMapping` or `null`.
|
|
@@ -78,14 +79,14 @@ type OnyxUpdate =
|
|
|
78
79
|
| {
|
|
79
80
|
onyxMethod: typeof METHOD.MERGE;
|
|
80
81
|
key: TKey;
|
|
81
|
-
value:
|
|
82
|
+
value: PartialDeep<KeyValueMapping[TKey]>;
|
|
82
83
|
};
|
|
83
84
|
}[OnyxKey]
|
|
84
85
|
| {
|
|
85
86
|
[TKey in CollectionKeyBase]: {
|
|
86
87
|
onyxMethod: typeof METHOD.MERGE_COLLECTION;
|
|
87
88
|
key: TKey;
|
|
88
|
-
value: Record<`${TKey}${string}`,
|
|
89
|
+
value: Record<`${TKey}${string}`, PartialDeep<KeyValueMapping[TKey]>>;
|
|
89
90
|
};
|
|
90
91
|
}[CollectionKeyBase];
|
|
91
92
|
|
|
@@ -201,7 +202,7 @@ declare function multiSet(data: Partial<NullableKeyValueMapping>): Promise<void>
|
|
|
201
202
|
* @param key ONYXKEYS key
|
|
202
203
|
* @param value Object or Array value to merge
|
|
203
204
|
*/
|
|
204
|
-
declare function merge<TKey extends OnyxKey>(key: TKey, value:
|
|
205
|
+
declare function merge<TKey extends OnyxKey>(key: TKey, value: NullableProperties<PartialDeep<KeyValueMapping[TKey]>>): Promise<void>;
|
|
205
206
|
|
|
206
207
|
/**
|
|
207
208
|
* Clear out all the data in the store
|
|
@@ -243,7 +244,10 @@ declare function clear(keysToPreserve?: OnyxKey[]): Promise<void>;
|
|
|
243
244
|
* @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
|
|
244
245
|
* @param collection Object collection keyed by individual collection member keys and values
|
|
245
246
|
*/
|
|
246
|
-
declare function mergeCollection<TKey extends CollectionKeyBase, TMap>(
|
|
247
|
+
declare function mergeCollection<TKey extends CollectionKeyBase, TMap>(
|
|
248
|
+
collectionKey: TKey,
|
|
249
|
+
collection: Collection<TKey, TMap, PartialDeep<KeyValueMapping[TKey]>>,
|
|
250
|
+
): Promise<void>;
|
|
247
251
|
|
|
248
252
|
/**
|
|
249
253
|
* Insert API responses and lifecycle data into Onyx
|
package/lib/Onyx.js
CHANGED
|
@@ -1041,13 +1041,7 @@ function set(key, value) {
|
|
|
1041
1041
|
Logger.logAlert(`Onyx.set() called after Onyx.merge() for key: ${key}. It is recommended to use set() or merge() not both.`);
|
|
1042
1042
|
}
|
|
1043
1043
|
|
|
1044
|
-
|
|
1045
|
-
// utils.fastMerge recursively goes through the object and removes all null values
|
|
1046
|
-
// Passing two identical objects as source and target to fastMerge will not change it, but only remove the null values
|
|
1047
|
-
let valueWithNullRemoved = value;
|
|
1048
|
-
if (typeof value === 'object' && !_.isArray(value)) {
|
|
1049
|
-
valueWithNullRemoved = utils.fastMerge(value, value);
|
|
1050
|
-
}
|
|
1044
|
+
const valueWithNullRemoved = utils.removeNullObjectValues(value);
|
|
1051
1045
|
|
|
1052
1046
|
const hasChanged = cache.hasValueChanged(key, valueWithNullRemoved);
|
|
1053
1047
|
|
|
@@ -1104,10 +1098,9 @@ function multiSet(data) {
|
|
|
1104
1098
|
* @private
|
|
1105
1099
|
* @param {*} existingValue
|
|
1106
1100
|
* @param {Array<*>} changes Array of changes that should be applied to the existing value
|
|
1107
|
-
* @param {Boolean} shouldRemoveNullObjectValues
|
|
1108
1101
|
* @returns {*}
|
|
1109
1102
|
*/
|
|
1110
|
-
function applyMerge(existingValue, changes
|
|
1103
|
+
function applyMerge(existingValue, changes) {
|
|
1111
1104
|
const lastChange = _.last(changes);
|
|
1112
1105
|
|
|
1113
1106
|
if (_.isArray(lastChange)) {
|
|
@@ -1116,7 +1109,7 @@ function applyMerge(existingValue, changes, shouldRemoveNullObjectValues) {
|
|
|
1116
1109
|
|
|
1117
1110
|
if (_.some(changes, _.isObject)) {
|
|
1118
1111
|
// Object values are then merged one after the other
|
|
1119
|
-
return _.reduce(changes, (modifiedData, change) => utils.fastMerge(modifiedData, change
|
|
1112
|
+
return _.reduce(changes, (modifiedData, change) => utils.fastMerge(modifiedData, change),
|
|
1120
1113
|
existingValue || {});
|
|
1121
1114
|
}
|
|
1122
1115
|
|
|
@@ -1164,8 +1157,7 @@ function merge(key, changes) {
|
|
|
1164
1157
|
.then((existingValue) => {
|
|
1165
1158
|
try {
|
|
1166
1159
|
// 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)
|
|
1167
|
-
|
|
1168
|
-
let batchedChanges = applyMerge(undefined, mergeQueue[key], false);
|
|
1160
|
+
let batchedChanges = applyMerge(undefined, mergeQueue[key]);
|
|
1169
1161
|
|
|
1170
1162
|
if (_.isNull(batchedChanges)) {
|
|
1171
1163
|
return remove(key);
|
|
@@ -1180,16 +1172,15 @@ function merge(key, changes) {
|
|
|
1180
1172
|
delete mergeQueuePromise[key];
|
|
1181
1173
|
|
|
1182
1174
|
// After that we merge the batched changes with the existing value
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
const modifiedData = shouldOverwriteExistingValue ? batchedChanges : applyMerge(existingValue, [batchedChanges], true);
|
|
1175
|
+
const updatedValue = shouldOverwriteExistingValue ? batchedChanges : applyMerge(existingValue, [batchedChanges]);
|
|
1176
|
+
const modifiedData = utils.removeNullObjectValues(updatedValue);
|
|
1186
1177
|
|
|
1187
1178
|
// On native platforms we use SQLite which utilises JSON_PATCH to merge changes.
|
|
1188
1179
|
// JSON_PATCH generally removes top-level nullish values from the stored object.
|
|
1189
|
-
// When there is no existing value though, SQLite will just insert the changes as a new value and thus the
|
|
1190
|
-
// Therefore we need to remove
|
|
1180
|
+
// When there is no existing value though, SQLite will just insert the changes as a new value and thus the top-level nullish values won't be removed.
|
|
1181
|
+
// Therefore we need to remove nullish values from the `batchedChanges` which are sent to the SQLite, if no existing value is present.
|
|
1191
1182
|
if (!existingValue) {
|
|
1192
|
-
batchedChanges =
|
|
1183
|
+
batchedChanges = utils.removeNullObjectValues(batchedChanges);
|
|
1193
1184
|
}
|
|
1194
1185
|
|
|
1195
1186
|
const hasChanged = cache.hasValueChanged(key, modifiedData);
|
|
@@ -56,7 +56,7 @@ const provider = {
|
|
|
56
56
|
const upsertMany = _.map(pairs, ([key, value], index) => {
|
|
57
57
|
const prev = values[index];
|
|
58
58
|
const newValue = _.isObject(prev) ? utils.fastMerge(prev, value) : value;
|
|
59
|
-
return promisifyRequest(store.put(newValue, key));
|
|
59
|
+
return promisifyRequest(store.put(utils.removeNullObjectValues(newValue), key));
|
|
60
60
|
});
|
|
61
61
|
return Promise.all(upsertMany);
|
|
62
62
|
});
|
package/lib/types.d.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import {Merge} from 'type-fest';
|
|
2
|
-
import {BuiltIns} from 'type-fest/source/internal';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Represents a deeply nested record. It maps keys to values,
|
|
6
5
|
* and those values can either be of type `TValue` or further nested `DeepRecord` instances.
|
|
7
6
|
*/
|
|
8
|
-
type DeepRecord<TKey extends string | number | symbol, TValue> = {
|
|
9
|
-
[key: string]: TValue | DeepRecord<TKey, TValue>;
|
|
10
|
-
};
|
|
7
|
+
type DeepRecord<TKey extends string | number | symbol, TValue> = {[key: string]: TValue | DeepRecord<TKey, TValue>};
|
|
11
8
|
|
|
12
9
|
/**
|
|
13
10
|
* Represents type options to configure all Onyx methods.
|
|
@@ -183,42 +180,26 @@ type OnyxEntry<TOnyxValue> = TOnyxValue | null;
|
|
|
183
180
|
*/
|
|
184
181
|
type OnyxCollection<TOnyxValue> = OnyxEntry<Record<string, TOnyxValue | null>>;
|
|
185
182
|
|
|
186
|
-
type NonTransformableTypes =
|
|
187
|
-
| BuiltIns
|
|
188
|
-
| ((...args: any[]) => unknown)
|
|
189
|
-
| Map<unknown, unknown>
|
|
190
|
-
| Set<unknown>
|
|
191
|
-
| ReadonlyMap<unknown, unknown>
|
|
192
|
-
| ReadonlySet<unknown>
|
|
193
|
-
| unknown[]
|
|
194
|
-
| readonly unknown[];
|
|
195
|
-
|
|
196
183
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* @example
|
|
200
|
-
* const settings: Settings = {
|
|
201
|
-
* textEditor: {
|
|
202
|
-
* fontSize: 14;
|
|
203
|
-
* fontColor: '#000000';
|
|
204
|
-
* fontWeight: 400;
|
|
205
|
-
* }
|
|
206
|
-
* autosave: true;
|
|
207
|
-
* };
|
|
184
|
+
* The `NullableProperties<T>` sets the values of all properties in `T` to be nullable (i.e., `| null`).
|
|
185
|
+
* It doesn't recurse into nested property values, this means it applies the nullability only to the top-level properties.
|
|
208
186
|
*
|
|
209
|
-
*
|
|
210
|
-
* return {...settings, ...savedSettings};
|
|
211
|
-
* }
|
|
212
|
-
*
|
|
213
|
-
* settings = applySavedSettings({textEditor: {fontWeight: 500, fontColor: null}});
|
|
187
|
+
* @template T The type of the properties to convert to nullable properties.
|
|
214
188
|
*/
|
|
215
|
-
type
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
Same as `NullishDeep`, but accepts only `object`s as inputs. Internal helper for `NullishDeep`.
|
|
219
|
-
*/
|
|
220
|
-
type NullishObjectDeep<ObjectType extends object> = {
|
|
221
|
-
[KeyType in keyof ObjectType]?: NullishDeep<ObjectType[KeyType]> | null;
|
|
189
|
+
type NullableProperties<T> = {
|
|
190
|
+
[P in keyof T]: T[P] | null;
|
|
222
191
|
};
|
|
223
192
|
|
|
224
|
-
export {
|
|
193
|
+
export {
|
|
194
|
+
CollectionKey,
|
|
195
|
+
CollectionKeyBase,
|
|
196
|
+
CustomTypeOptions,
|
|
197
|
+
DeepRecord,
|
|
198
|
+
Key,
|
|
199
|
+
KeyValueMapping,
|
|
200
|
+
OnyxCollection,
|
|
201
|
+
OnyxEntry,
|
|
202
|
+
OnyxKey,
|
|
203
|
+
Selector,
|
|
204
|
+
NullableProperties,
|
|
205
|
+
};
|
package/lib/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import _ from 'underscore';
|
|
1
|
+
import * as _ from 'underscore';
|
|
2
2
|
|
|
3
3
|
function areObjectsEmpty(a, b) {
|
|
4
4
|
return (
|
|
@@ -25,12 +25,9 @@ function isMergeableObject(val) {
|
|
|
25
25
|
/**
|
|
26
26
|
* @param {Object} target
|
|
27
27
|
* @param {Object} source
|
|
28
|
-
* @param {Boolean} shouldRemoveNullObjectValues
|
|
29
28
|
* @returns {Object}
|
|
30
29
|
*/
|
|
31
|
-
function mergeObject(target, source
|
|
32
|
-
const targetAndSourceIdentical = target === source;
|
|
33
|
-
|
|
30
|
+
function mergeObject(target, source) {
|
|
34
31
|
const destination = {};
|
|
35
32
|
if (isMergeableObject(target)) {
|
|
36
33
|
// lodash adds a small overhead so we don't use it here
|
|
@@ -38,13 +35,6 @@ function mergeObject(target, source, shouldRemoveNullObjectValues = true) {
|
|
|
38
35
|
const targetKeys = Object.keys(target);
|
|
39
36
|
for (let i = 0; i < targetKeys.length; ++i) {
|
|
40
37
|
const key = targetKeys[i];
|
|
41
|
-
|
|
42
|
-
// If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
|
|
43
|
-
if (shouldRemoveNullObjectValues && (target[key] === null || source[key] === null)) {
|
|
44
|
-
// eslint-disable-next-line no-continue
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
38
|
destination[key] = target[key];
|
|
49
39
|
}
|
|
50
40
|
}
|
|
@@ -54,22 +44,15 @@ function mergeObject(target, source, shouldRemoveNullObjectValues = true) {
|
|
|
54
44
|
const sourceKeys = Object.keys(source);
|
|
55
45
|
for (let i = 0; i < sourceKeys.length; ++i) {
|
|
56
46
|
const key = sourceKeys[i];
|
|
57
|
-
|
|
58
|
-
// If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
|
|
59
|
-
if (shouldRemoveNullObjectValues && source[key] === null) {
|
|
47
|
+
if (source[key] === undefined) {
|
|
60
48
|
// eslint-disable-next-line no-continue
|
|
61
49
|
continue;
|
|
62
50
|
}
|
|
63
|
-
|
|
64
51
|
if (!isMergeableObject(source[key]) || !target[key]) {
|
|
65
|
-
if (targetAndSourceIdentical) {
|
|
66
|
-
// eslint-disable-next-line no-continue
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
52
|
destination[key] = source[key];
|
|
70
53
|
} else {
|
|
71
54
|
// eslint-disable-next-line no-use-before-define
|
|
72
|
-
destination[key] = fastMerge(target[key], source[key]
|
|
55
|
+
destination[key] = fastMerge(target[key], source[key]);
|
|
73
56
|
}
|
|
74
57
|
}
|
|
75
58
|
|
|
@@ -77,26 +60,39 @@ function mergeObject(target, source, shouldRemoveNullObjectValues = true) {
|
|
|
77
60
|
}
|
|
78
61
|
|
|
79
62
|
/**
|
|
80
|
-
* Merges two objects and removes null values if "shouldRemoveNullObjectValues" is set to true
|
|
81
|
-
*
|
|
82
|
-
* We generally want to remove null values from objects written to disk and cache, because it decreases the amount of data stored in memory and on disk.
|
|
83
|
-
* On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
|
|
84
|
-
* To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations.
|
|
85
|
-
*
|
|
86
63
|
* @param {Object|Array} target
|
|
87
64
|
* @param {Object|Array} source
|
|
88
|
-
* @param {Boolean} shouldRemoveNullObjectValues
|
|
89
65
|
* @returns {Object|Array}
|
|
90
66
|
*/
|
|
91
|
-
function fastMerge(target, source
|
|
67
|
+
function fastMerge(target, source) {
|
|
92
68
|
// We have to ignore arrays and nullish values here,
|
|
93
69
|
// otherwise "mergeObject" will throw an error,
|
|
94
70
|
// because it expects an object as "source"
|
|
95
|
-
if (_.isArray(source) || source
|
|
71
|
+
if (_.isArray(source) || _.isNull(source) || _.isUndefined(source)) {
|
|
96
72
|
return source;
|
|
97
73
|
}
|
|
98
|
-
return mergeObject(target, source
|
|
74
|
+
return mergeObject(target, source);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* We generally want to remove top-level nullish values from objects written to disk and cache, because it decreases the amount of data stored in memory and on disk.
|
|
79
|
+
* On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
|
|
80
|
+
* To be consistent with the behaviour for merge, we'll also want to remove nullish values for "set" operations.
|
|
81
|
+
* On web, IndexedDB will keep the top-level keys along with a null value and this uses up storage and memory.
|
|
82
|
+
* This method will ensure that keys for null values are removed before an object is written to disk and cache so that all platforms are storing the data in the same efficient way.
|
|
83
|
+
* @private
|
|
84
|
+
* @param {*} value
|
|
85
|
+
* @returns {*}
|
|
86
|
+
*/
|
|
87
|
+
function removeNullObjectValues(value) {
|
|
88
|
+
if (_.isArray(value) || !_.isObject(value)) {
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const objectWithoutNullObjectValues = _.omit(value, objectValue => _.isNull(objectValue));
|
|
93
|
+
|
|
94
|
+
return objectWithoutNullObjectValues;
|
|
99
95
|
}
|
|
100
96
|
|
|
101
|
-
export default {areObjectsEmpty, fastMerge};
|
|
97
|
+
export default {removeNullObjectValues, areObjectsEmpty, fastMerge};
|
|
102
98
|
|
package/lib/withOnyx.js
CHANGED
|
@@ -146,7 +146,13 @@ export default function (mapOnyxToState, shouldDelayUpdates = false) {
|
|
|
146
146
|
const prevValue = this.state[statePropertyName];
|
|
147
147
|
|
|
148
148
|
// If the component is not loading (read "mounting"), then we can just update the state
|
|
149
|
-
|
|
149
|
+
// There is a small race condition.
|
|
150
|
+
// When calling setWithOnyxState we delete the tempState object that is used to hold temporary state updates while the HOC is gathering data.
|
|
151
|
+
// However the loading flag is only set on the setState callback down below. setState however is an async operation that is also batched,
|
|
152
|
+
// therefore there is a small window of time where the loading flag is not false but the tempState is already gone
|
|
153
|
+
// (while the update is queued and waiting to be applied).
|
|
154
|
+
// This simply bypasses the loading check if the tempState is gone and the update can be safely queued with a normal setStateProxy.
|
|
155
|
+
if (!this.state.loading || !this.tempState) {
|
|
150
156
|
// Performance optimization, do not trigger update with same values
|
|
151
157
|
if (prevValue === val || utils.areObjectsEmpty(prevValue, val)) {
|
|
152
158
|
return;
|