react-native-onyx 1.0.60 → 1.0.62

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/lib/Onyx.js CHANGED
@@ -901,7 +901,6 @@ function broadcastUpdate(key, value, hasChanged, method) {
901
901
  }
902
902
 
903
903
  /**
904
- * @private
905
904
  * @param {String} key
906
905
  * @returns {Boolean}
907
906
  */
@@ -1409,6 +1408,7 @@ const Onyx = {
1409
1408
  METHOD,
1410
1409
  setMemoryOnlyKeys,
1411
1410
  tryGetCachedValue,
1411
+ hasPendingMergeForKey,
1412
1412
  };
1413
1413
 
1414
1414
  /**
@@ -1,10 +1,10 @@
1
1
  /**
2
- * This file is here to wrap LocalForage with a layer that provides data-changed events like the ones that exist
2
+ * This file is here to wrap IDBKeyVal with a layer that provides data-changed events like the ones that exist
3
3
  * when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when
4
4
  * data changes and then stay up-to-date with everything happening in Onyx.
5
5
  */
6
6
  import _ from 'underscore';
7
- import Storage from './providers/LocalForage';
7
+ import Storage from './providers/IDBKeyVal';
8
8
 
9
9
  const SYNC_ONYX = 'SYNC_ONYX';
10
10
 
@@ -8,7 +8,7 @@ const set = jest.fn((key, value) => {
8
8
  return Promise.resolve(value);
9
9
  });
10
10
 
11
- const localForageMock = {
11
+ const idbKeyvalMock = {
12
12
  setItem(key, value) {
13
13
  return set(key, value);
14
14
  },
@@ -57,23 +57,23 @@ const localForageMock = {
57
57
  config() {},
58
58
  };
59
59
 
60
- const localForageMockSpy = {
61
- localForageSet: set,
62
- setItem: jest.fn(localForageMock.setItem),
63
- getItem: jest.fn(localForageMock.getItem),
64
- removeItem: jest.fn(localForageMock.removeItem),
65
- removeItems: jest.fn(localForageMock.removeItems),
66
- clear: jest.fn(localForageMock.clear),
67
- getAllKeys: jest.fn(localForageMock.getAllKeys),
68
- config: jest.fn(localForageMock.config),
69
- multiGet: jest.fn(localForageMock.multiGet),
70
- multiSet: jest.fn(localForageMock.multiSet),
71
- multiMerge: jest.fn(localForageMock.multiMerge),
72
- mergeItem: jest.fn(localForageMock.mergeItem),
60
+ const idbKeyvalMockSpy = {
61
+ idbKeyvalSet: set,
62
+ setItem: jest.fn(idbKeyvalMock.setItem),
63
+ getItem: jest.fn(idbKeyvalMock.getItem),
64
+ removeItem: jest.fn(idbKeyvalMock.removeItem),
65
+ removeItems: jest.fn(idbKeyvalMock.removeItems),
66
+ clear: jest.fn(idbKeyvalMock.clear),
67
+ getAllKeys: jest.fn(idbKeyvalMock.getAllKeys),
68
+ config: jest.fn(idbKeyvalMock.config),
69
+ multiGet: jest.fn(idbKeyvalMock.multiGet),
70
+ multiSet: jest.fn(idbKeyvalMock.multiSet),
71
+ multiMerge: jest.fn(idbKeyvalMock.multiMerge),
72
+ mergeItem: jest.fn(idbKeyvalMock.mergeItem),
73
73
  getStorageMap: jest.fn(() => storageMapInternal),
74
74
  setInitialMockData: jest.fn((data) => {
75
75
  storageMapInternal = data;
76
76
  }),
77
77
  };
78
78
 
79
- export default localForageMockSpy;
79
+ export default idbKeyvalMockSpy;
@@ -0,0 +1,110 @@
1
+ import {
2
+ set,
3
+ keys,
4
+ getMany,
5
+ setMany,
6
+ get,
7
+ clear,
8
+ del,
9
+ delMany,
10
+ createStore,
11
+ promisifyRequest,
12
+ } from 'idb-keyval';
13
+ import _ from 'underscore';
14
+ import fastMerge from '../../fastMerge';
15
+
16
+ const customStore = createStore('OnyxDB', 'keyvaluepairs');
17
+
18
+ const provider = {
19
+ /**
20
+ * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
21
+ * @param {String} key
22
+ * @param {*} value
23
+ * @return {Promise<void>}
24
+ */
25
+ setItem: (key, value) => set(key, value, customStore),
26
+
27
+ /**
28
+ * Get multiple key-value pairs for the give array of keys in a batch.
29
+ * This is optimized to use only one database transaction.
30
+ * @param {String[]} keysParam
31
+ * @return {Promise<Array<[key, value]>>}
32
+ */
33
+ multiGet: keysParam => getMany(keysParam, customStore)
34
+ .then(values => _.map(values, (value, index) => [keysParam[index], value])),
35
+
36
+ /**
37
+ * Multiple merging of existing and new values in a batch
38
+ * @param {Array<[key, value]>} pairs
39
+ * @return {Promise<void>}
40
+ */
41
+ multiMerge: pairs => customStore('readwrite', (store) => {
42
+ // Note: we are using the manual store transaction here, to fit the read and update
43
+ // of the items in one transaction to achieve best performance.
44
+
45
+ const getValues = Promise.all(_.map(pairs, ([key]) => promisifyRequest(store.get(key))));
46
+
47
+ return getValues.then((values) => {
48
+ const upsertMany = _.map(pairs, ([key, value], index) => {
49
+ const prev = values[index];
50
+ const newValue = _.isObject(prev) ? fastMerge(prev, value) : value;
51
+ return promisifyRequest(store.put(newValue, key));
52
+ });
53
+ return Promise.all(upsertMany);
54
+ });
55
+ }),
56
+
57
+ /**
58
+ * Merging an existing value with a new one
59
+ * @param {String} key
60
+ * @param {any} _changes - not used, as we rely on the pre-merged data from the `modifiedData`
61
+ * @param {any} modifiedData - the pre-merged data from `Onyx.applyMerge`
62
+ * @return {Promise<void>}
63
+ */
64
+ mergeItem(key, _changes, modifiedData) {
65
+ return provider.multiMerge([[key, modifiedData]]);
66
+ },
67
+
68
+ /**
69
+ * Stores multiple key-value pairs in a batch
70
+ * @param {Array<[key, value]>} pairs
71
+ * @return {Promise<void>}
72
+ */
73
+ multiSet: pairs => setMany(pairs, customStore),
74
+
75
+ /**
76
+ * Clear everything from storage and also stops the SyncQueue from adding anything more to storage
77
+ * @returns {Promise<void>}
78
+ */
79
+ clear: () => clear(customStore),
80
+
81
+ /**
82
+ * Returns all keys available in storage
83
+ * @returns {Promise<String[]>}
84
+ */
85
+ getAllKeys: () => keys(customStore),
86
+
87
+ /**
88
+ * Get the value of a given key or return `null` if it's not available in storage
89
+ * @param {String} key
90
+ * @return {Promise<*>}
91
+ */
92
+ getItem: key => get(key, customStore),
93
+
94
+ /**
95
+ * Remove given key and it's value from storage
96
+ * @param {String} key
97
+ * @returns {Promise<void>}
98
+ */
99
+ removeItem: key => del(key, customStore),
100
+
101
+ /**
102
+ * Remove given keys and their values from storage
103
+ *
104
+ * @param {Array} keysParam
105
+ * @returns {Promise}
106
+ */
107
+ removeItems: keysParam => delMany(keysParam, customStore),
108
+ };
109
+
110
+ export default provider;
package/lib/withOnyx.js CHANGED
@@ -41,7 +41,18 @@ export default function (mapOnyxToState) {
41
41
  const key = Str.result(mapping.key, props);
42
42
  const value = Onyx.tryGetCachedValue(key, mapping);
43
43
 
44
- if (value !== undefined) {
44
+ /**
45
+ * If we have a pending merge for a key it could mean that data is being set via Onyx.merge() and someone expects a component to have this data immediately.
46
+ *
47
+ * @example
48
+ *
49
+ * Onyx.merge('report_123', value);
50
+ * Navigation.navigate(route); // Where "route" expects the "value" to be available immediately once rendered.
51
+ *
52
+ * In reality, Onyx.merge() will only update the subscriber after all merges have been batched and the previous value is retrieved via a get() (returns a promise).
53
+ * So, we won't use the cache optimization here as it will lead us to arbitrarily defer various actions in the application code.
54
+ */
55
+ if (value !== undefined && !Onyx.hasPendingMergeForKey(key)) {
45
56
  // eslint-disable-next-line no-param-reassign
46
57
  resultObj[propertyName] = value;
47
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "1.0.60",
3
+ "version": "1.0.62",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",
@@ -59,10 +59,10 @@
59
59
  "eslint-config-expensify": "^2.0.38",
60
60
  "eslint-plugin-jsx-a11y": "^6.6.1",
61
61
  "eslint-plugin-react": "^7.31.10",
62
+ "idb-keyval": "^6.2.1",
62
63
  "jest": "^26.5.2",
63
64
  "jest-cli": "^26.5.2",
64
65
  "jsdoc-to-markdown": "^7.1.0",
65
- "localforage": "^1.10.0",
66
66
  "metro-react-native-babel-preset": "^0.72.3",
67
67
  "prop-types": "^15.7.2",
68
68
  "react": "18.2.0",
@@ -76,23 +76,19 @@
76
76
  "webpack-merge": "^5.8.0"
77
77
  },
78
78
  "peerDependencies": {
79
- "localforage": "^1.10.0",
80
- "localforage-removeitems": "^1.4.0",
79
+ "idb-keyval": "^6.2.1",
81
80
  "react": ">=18.1.0",
82
81
  "react-native-performance": "^4.0.0",
83
82
  "react-native-quick-sqlite": "^8.0.0-beta.2"
84
83
  },
85
84
  "peerDependenciesMeta": {
86
- "react-native-performance": {
87
- "optional": true
88
- },
89
- "react-native-quick-sqlite": {
85
+ "idb-keyval": {
90
86
  "optional": true
91
87
  },
92
- "localforage": {
88
+ "react-native-performance": {
93
89
  "optional": true
94
90
  },
95
- "localforage-removeitems": {
91
+ "react-native-quick-sqlite": {
96
92
  "optional": true
97
93
  }
98
94
  },
@@ -1,158 +0,0 @@
1
- /**
2
- * @file
3
- * The storage provider based on localforage allows us to store most anything in its
4
- * natural form in the underlying DB without having to stringify or de-stringify it
5
- */
6
-
7
- import localforage from 'localforage';
8
- import _ from 'underscore';
9
- import {extendPrototype} from 'localforage-removeitems';
10
- import SyncQueue from '../../SyncQueue';
11
- import * as Str from '../../Str';
12
- import fastMerge from '../../fastMerge';
13
-
14
- extendPrototype(localforage);
15
-
16
- localforage.config({
17
- name: 'OnyxDB',
18
- });
19
-
20
- /**
21
- * Keys that will not ever be persisted to disk.
22
- */
23
- let memoryOnlyKeys = [];
24
-
25
- const provider = {
26
- /**
27
- * Writing very quickly to IndexedDB causes performance issues and can lock up the page and lead to jank.
28
- * So, we are slowing this process down by waiting until one write is complete before moving on
29
- * to the next.
30
- */
31
- setItemQueue: new SyncQueue(({key, value, shouldMerge}) => {
32
- if (_.find(memoryOnlyKeys, noCacheKey => Str.startsWith(key, noCacheKey))) {
33
- return Promise.resolve();
34
- }
35
-
36
- if (shouldMerge) {
37
- return localforage.getItem(key)
38
- .then((existingValue) => {
39
- const newValue = _.isObject(existingValue)
40
-
41
- // lodash adds a small overhead so we don't use it here
42
- // eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
43
- ? Object.assign({}, fastMerge(existingValue, value))
44
- : value;
45
- return localforage.setItem(key, newValue);
46
- });
47
- }
48
-
49
- return localforage.setItem(key, value);
50
- }),
51
-
52
- /**
53
- * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
54
- * @param {String} key
55
- * @param {*} value
56
- * @return {Promise<void>}
57
- */
58
- setItem(key, value) {
59
- return this.setItemQueue.push({key, value});
60
- },
61
-
62
- /**
63
- * Get multiple key-value pairs for the give array of keys in a batch
64
- * @param {String[]} keys
65
- * @return {Promise<Array<[key, value]>>}
66
- */
67
- multiGet(keys) {
68
- const pairs = _.map(
69
- keys,
70
- key => localforage.getItem(key)
71
- .then(value => [key, value]),
72
- );
73
-
74
- return Promise.all(pairs);
75
- },
76
-
77
- /**
78
- * Multiple merging of existing and new values in a batch
79
- * @param {Array<[key, value]>} pairs
80
- * @return {Promise<void>}
81
- */
82
- multiMerge(pairs) {
83
- const tasks = _.map(pairs, ([key, value]) => this.setItemQueue.push({key, value, shouldMerge: true}));
84
-
85
- // We're returning Promise.resolve, otherwise the array of task results will be returned to the caller
86
- return Promise.all(tasks).then(() => Promise.resolve());
87
- },
88
-
89
- /**
90
- * Merging an existing value with a new one
91
- * @param {String} key
92
- * @param {any} _changes - not used, as we rely on the pre-merged data from the `modifiedData`
93
- * @param {any} modifiedData - the pre-merged data from `Onyx.applyMerge`
94
- * @return {Promise<void>}
95
- */
96
- mergeItem(key, _changes, modifiedData) {
97
- return this.setItem(key, modifiedData);
98
- },
99
-
100
- /**
101
- * Stores multiple key-value pairs in a batch
102
- * @param {Array<[key, value]>} pairs
103
- * @return {Promise<void>}
104
- */
105
- multiSet(pairs) {
106
- // We're returning Promise.resolve, otherwise the array of task results will be returned to the caller
107
- const tasks = _.map(pairs, ([key, value]) => this.setItem(key, value));
108
- return Promise.all(tasks).then(() => Promise.resolve());
109
- },
110
-
111
- /**
112
- * Clear everything from storage and also stops the SyncQueue from adding anything more to storage
113
- * @returns {Promise<void>}
114
- */
115
- clear() {
116
- this.setItemQueue.abort();
117
- return localforage.clear();
118
- },
119
-
120
- /**
121
- * Returns all keys available in storage
122
- * @returns {Promise<String[]>}
123
- */
124
- getAllKeys: localforage.keys,
125
-
126
- /**
127
- * Get the value of a given key or return `null` if it's not available in storage
128
- * @param {String} key
129
- * @return {Promise<*>}
130
- */
131
- getItem: localforage.getItem,
132
-
133
- /**
134
- * Remove given key and it's value from storage
135
- * @param {String} key
136
- * @returns {Promise<void>}
137
- */
138
- removeItem: localforage.removeItem,
139
-
140
- /**
141
- * Remove given keys and their values from storage
142
- *
143
- * @param {Array} keys
144
- * @returns {Promise}
145
- */
146
- removeItems(keys) {
147
- return localforage.removeItems(keys);
148
- },
149
-
150
- /**
151
- * @param {string[]} keyList
152
- */
153
- setMemoryOnlyKeys(keyList) {
154
- memoryOnlyKeys = keyList;
155
- },
156
- };
157
-
158
- export default provider;