react-native-onyx 1.0.55 → 1.0.57

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
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable no-continue */
2
2
  import {deepEqual} from 'fast-equals';
3
- import lodashGet from 'lodash/get';
4
3
  import _ from 'underscore';
5
4
  import * as Logger from './Logger';
6
5
  import cache from './OnyxCache';
@@ -48,17 +47,13 @@ let defaultKeyStates = {};
48
47
  const deferredInitTask = createDeferredTask();
49
48
 
50
49
  /**
51
- * Uses a selector string or function to return a simplified version of sourceData
50
+ * Uses a selector function to return a simplified version of sourceData
52
51
  * @param {Mixed} sourceData
53
- * @param {String|Function} selector
52
+ * @param {Function} selector Function that takes sourceData and returns a simplified version of it
54
53
  * @param {Object} [withOnyxInstanceState]
55
- * If it's a string, the selector is passed to lodashGet on the sourceData
56
- * If it's a function, it is passed the sourceData and it should return the simplified data
57
54
  * @returns {Mixed}
58
55
  */
59
- const getSubsetOfData = (sourceData, selector, withOnyxInstanceState) => (_.isFunction(selector)
60
- ? selector(sourceData, withOnyxInstanceState)
61
- : lodashGet(sourceData, selector));
56
+ const getSubsetOfData = (sourceData, selector, withOnyxInstanceState) => selector(sourceData, withOnyxInstanceState);
62
57
 
63
58
  /**
64
59
  * Takes a collection of items (eg. {testKey_1:{a:'a'}, testKey_2:{b:'b'}})
@@ -184,6 +179,46 @@ function isSafeEvictionKey(testKey) {
184
179
  return _.some(evictionAllowList, key => isKeyMatch(key, testKey));
185
180
  }
186
181
 
182
+ /**
183
+ * Tries to get a value from the cache. If the value is not present in cache it will return the default value or undefined.
184
+ * If the requested key is a collection, it will return an object with all the collection members.
185
+ *
186
+ * @param {String} key
187
+ * @param {Object} mapping
188
+ * @returns {Mixed}
189
+ */
190
+ function tryGetCachedValue(key, mapping = {}) {
191
+ let val = cache.getValue(key);
192
+
193
+ if (isCollectionKey(key)) {
194
+ const allKeys = cache.getAllKeys();
195
+ const matchingKeys = _.filter(allKeys, k => k.startsWith(key));
196
+ const values = _.reduce(matchingKeys, (finalObject, matchedKey) => {
197
+ const cachedValue = cache.getValue(matchedKey);
198
+ if (cachedValue) {
199
+ // This is permissible because we're in the process of constructing the final object in a reduce function.
200
+ // eslint-disable-next-line no-param-reassign
201
+ finalObject[matchedKey] = cachedValue;
202
+ }
203
+ return finalObject;
204
+ }, {});
205
+ if (_.isEmpty(values)) {
206
+ return;
207
+ }
208
+ val = values;
209
+ }
210
+
211
+ if (mapping.selector) {
212
+ const state = mapping.withOnyxInstance ? mapping.withOnyxInstance.state : undefined;
213
+ if (isCollectionKey(key)) {
214
+ return reduceCollectionWithSelector(val, mapping.selector, state);
215
+ }
216
+ return getSubsetOfData(val, mapping.selector, state);
217
+ }
218
+
219
+ return val;
220
+ }
221
+
187
222
  /**
188
223
  * Remove a key from the recently accessed key list.
189
224
  *
@@ -666,11 +701,10 @@ function getCollectionDataAndSendAsObject(matchingKeys, mapping) {
666
701
  * @param {Boolean} [mapping.initWithStoredValues] If set to false, then no data will be prefilled into the
667
702
  * component
668
703
  * @param {Boolean} [mapping.waitForCollectionCallback] If set to true, it will return the entire collection to the callback as a single object
669
- * @param {String|Function} [mapping.selector] THIS PARAM IS ONLY USED WITH withOnyx(). If included, this will be used to subscribe to a subset of an Onyx key's data.
670
- * If the selector is a string, the selector is passed to lodashGet on the sourceData. If the selector is a function, the sourceData and withOnyx state are
671
- * passed to the selector and should return the simplified data. Using this setting on `withOnyx` can have very positive performance benefits because the component
672
- * will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally cause the component to re-render (and that can
673
- * be expensive from a performance standpoint).
704
+ * @param {Function} [mapping.selector] THIS PARAM IS ONLY USED WITH withOnyx(). If included, this will be used to subscribe to a subset of an Onyx key's data.
705
+ * The sourceData and withOnyx state are passed to the selector and should return the simplified data. Using this setting on `withOnyx` can have very positive
706
+ * performance benefits because the component will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally
707
+ * cause the component to re-render (and that can be expensive from a performance standpoint).
674
708
  * @returns {Number} an ID to use when calling disconnect
675
709
  */
676
710
  function connect(mapping) {
@@ -1353,6 +1387,7 @@ const Onyx = {
1353
1387
  isSafeEvictionKey,
1354
1388
  METHOD,
1355
1389
  setMemoryOnlyKeys,
1390
+ tryGetCachedValue,
1356
1391
  };
1357
1392
 
1358
1393
  /**
package/lib/withOnyx.js CHANGED
@@ -37,13 +37,25 @@ export default function (mapOnyxToState) {
37
37
  // disconnected. It is a key value store with the format {[mapping.key]: connectionID}.
38
38
  this.activeConnectionIDs = {};
39
39
 
40
+ const cachedState = _.reduce(mapOnyxToState, (resultObj, mapping, propertyName) => {
41
+ const key = Str.result(mapping.key, props);
42
+ const value = Onyx.tryGetCachedValue(key, mapping);
43
+
44
+ if (value !== undefined) {
45
+ // eslint-disable-next-line no-param-reassign
46
+ resultObj[propertyName] = value;
47
+ }
48
+
49
+ return resultObj;
50
+ }, {});
51
+
52
+ // If we have all the data we need, then we can render the component immediately
53
+ cachedState.loading = _.size(cachedState) < requiredKeysForInit.length;
54
+
40
55
  // Object holding the temporary initial state for the component while we load the various Onyx keys
41
- this.tempState = {};
56
+ this.tempState = cachedState;
42
57
 
43
- this.state = {
44
- // If there are no required keys for init then we can render the wrapped component immediately
45
- loading: requiredKeysForInit.length > 0,
46
- };
58
+ this.state = cachedState;
47
59
  }
48
60
 
49
61
  componentDidMount() {
@@ -60,7 +72,6 @@ export default function (mapOnyxToState) {
60
72
  _.each(mapOnyxToState, (mapping, propertyName) => {
61
73
  const previousKey = Str.result(mapping.key, prevProps);
62
74
  const newKey = Str.result(mapping.key, this.props);
63
-
64
75
  if (previousKey !== newKey) {
65
76
  Onyx.disconnect(this.activeConnectionIDs[previousKey], previousKey);
66
77
  delete this.activeConnectionIDs[previousKey];
@@ -88,6 +99,16 @@ export default function (mapOnyxToState) {
88
99
  * @param {*} val
89
100
  */
90
101
  setWithOnyxState(statePropertyName, val) {
102
+ // We might have loaded the values for the onyx keys/mappings already from the cache.
103
+ // In case we were able to load all the values upfront, the loading state will be false.
104
+ // However, Onyx.js will always call setWithOnyxState, as it doesn't know that this implementation
105
+ // already loaded the values from cache. Thus we have to check whether the value has changed
106
+ // before we set the state to prevent unnecessary renders.
107
+ const prevValue = this.state[statePropertyName];
108
+ if (!this.state.loading && prevValue === val) {
109
+ return;
110
+ }
111
+
91
112
  if (!this.state.loading) {
92
113
  this.setState({[statePropertyName]: val});
93
114
  return;
@@ -100,7 +121,14 @@ export default function (mapOnyxToState) {
100
121
  return;
101
122
  }
102
123
 
103
- this.setState({...this.tempState, loading: false});
124
+ const stateUpdate = {...this.tempState, loading: false};
125
+
126
+ // The state is set here manually, instead of using setState because setState is an async operation, meaning it might execute on the next tick,
127
+ // or at a later point in the microtask queue. That can lead to a race condition where
128
+ // setWithOnyxState is called before the state is actually set. This results in unreliable behavior when checking the loading state and has been mainly observed on fabric.
129
+ this.state = stateUpdate;
130
+
131
+ this.setState(stateUpdate); // Trigger a render
104
132
  delete this.tempState;
105
133
  }
106
134
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "1.0.55",
3
+ "version": "1.0.57",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",
@@ -41,7 +41,6 @@
41
41
  "dependencies": {
42
42
  "ascii-table": "0.0.9",
43
43
  "fast-equals": "^4.0.3",
44
- "lodash": "^4.17.21",
45
44
  "underscore": "^1.13.1"
46
45
  },
47
46
  "devDependencies": {