react-native-onyx 1.0.110 → 1.0.112

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/OnyxCache.js CHANGED
@@ -121,7 +121,7 @@ class OnyxCache {
121
121
 
122
122
  // lodash adds a small overhead so we don't use it here
123
123
  // eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
124
- this.storageMap = Object.assign({}, utils.fastMerge(this.storageMap, data));
124
+ this.storageMap = Object.assign({}, utils.fastMerge(this.storageMap, data, false));
125
125
 
126
126
  const storageKeys = this.getAllKeys();
127
127
  const mergedKeys = _.keys(data);
package/lib/withOnyx.js CHANGED
@@ -10,6 +10,10 @@ import Onyx from './Onyx';
10
10
  import * as Str from './Str';
11
11
  import utils from './utils';
12
12
 
13
+ // This is a list of keys that can exist on a `mapping`, but are not directly related to loading data from Onyx. When the keys of a mapping are looped over to check
14
+ // if a key has changed, it's a good idea to skip looking at these properties since they would have unexpected results.
15
+ const mappingPropertiesToIgnoreChangesTo = ['initialValue'];
16
+
13
17
  /**
14
18
  * Returns the display name of a component
15
19
  *
@@ -94,27 +98,52 @@ export default function (mapOnyxToState, shouldDelayUpdates = false) {
94
98
  }
95
99
 
96
100
  componentDidMount() {
101
+ const onyxDataFromState = getOnyxDataFromState(this.state, mapOnyxToState);
102
+
97
103
  // Subscribe each of the state properties to the proper Onyx key
98
104
  _.each(mapOnyxToState, (mapping, propertyName) => {
99
- this.connectMappingToOnyx(mapping, propertyName);
105
+ if (_.includes(mappingPropertiesToIgnoreChangesTo, propertyName)) {
106
+ return;
107
+ }
108
+ const key = Str.result(mapping.key, {...this.props, ...onyxDataFromState});
109
+ this.connectMappingToOnyx(mapping, propertyName, key);
100
110
  });
101
111
  this.checkEvictableKeys();
102
112
  }
103
113
 
104
- componentDidUpdate() {
105
- // When the state is passed to the key functions with Str.result(), omit anything
106
- // from state that was not part of the mapped keys.
114
+ componentDidUpdate(prevProps, prevState) {
115
+ // The whole purpose of this method is to check to see if a key that is subscribed to Onyx has changed, and then Onyx needs to be disconnected from the old
116
+ // key and connected to the new key.
117
+ // For example, a key could change if KeyB depends on data loading from Onyx for KeyA.
118
+ const isFirstTimeUpdatingAfterLoading = prevState.loading && !this.state.loading;
107
119
  const onyxDataFromState = getOnyxDataFromState(this.state, mapOnyxToState);
120
+ const prevOnyxDataFromState = getOnyxDataFromState(prevState, mapOnyxToState);
121
+
122
+ // This ensures that only one property is reconnecting at a time, or else it can lead to race conditions and infinite rendering loops. No fun!
123
+ let isReconnectingToOnyx = false;
108
124
 
109
- // If any of the mappings use data from the props, then when the props change, all the
110
- // connections need to be reconnected with the new props
111
125
  _.each(mapOnyxToState, (mapping, propName) => {
112
- const previousKey = mapping.previousKey;
126
+ // Some properties can be ignored and also return early if the component is already reconnecting to Onyx
127
+ if (_.includes(mappingPropertiesToIgnoreChangesTo, propName) || isReconnectingToOnyx) {
128
+ return;
129
+ }
130
+
131
+ // The previous key comes from either:
132
+ // 1) The initial key that was connected to (ie. set from `componentDidMount()`)
133
+ // 2) The updated props which caused `componentDidUpdate()` to run
134
+ // The first case cannot be used all the time because of race conditions where `componentDidUpdate()` can be triggered before connectingMappingToOnyx() is done
135
+ // (eg. if a user switches chats really quickly). In this case, it's much more stable to always look at the changes to prevProp and prevState to derive the key.
136
+ // The second case cannot be used all the time because the onyx data doesn't change the first time that `componentDidUpdate()` runs after loading. In this case,
137
+ // the `mapping.previousKey` must be used for the comparison or else this logic never detects that onyx data could have changed during the loading process.
138
+ const previousKey = isFirstTimeUpdatingAfterLoading
139
+ ? mapping.previousKey
140
+ : Str.result(mapping.key, {...prevProps, ...prevOnyxDataFromState});
113
141
  const newKey = Str.result(mapping.key, {...this.props, ...onyxDataFromState});
114
142
  if (previousKey !== newKey) {
143
+ isReconnectingToOnyx = true;
115
144
  Onyx.disconnect(this.activeConnectionIDs[previousKey], previousKey);
116
145
  delete this.activeConnectionIDs[previousKey];
117
- this.connectMappingToOnyx(mapping, propName);
146
+ this.connectMappingToOnyx(mapping, propName, newKey);
118
147
  }
119
148
  });
120
149
  this.checkEvictableKeys();
@@ -254,16 +283,13 @@ export default function (mapOnyxToState, shouldDelayUpdates = false) {
254
283
  * @param {string} statePropertyName the name of the state property that Onyx will add the data to
255
284
  * @param {boolean} [mapping.initWithStoredValues] If set to false, then no data will be prefilled into the
256
285
  * component
286
+ * @param {string} key to connect to Onyx with
257
287
  */
258
- connectMappingToOnyx(mapping, statePropertyName) {
259
- const key = Str.result(mapping.key, {...this.props, ...getOnyxDataFromState(this.state, mapOnyxToState)});
260
-
261
- // Remember the previous key so that if it ever changes, the component will reconnect to Onyx
262
- // in componentDidUpdate
263
- if (statePropertyName !== 'initialValue' && mapOnyxToState[statePropertyName]) {
264
- // eslint-disable-next-line no-param-reassign
265
- mapOnyxToState[statePropertyName].previousKey = key;
266
- }
288
+ connectMappingToOnyx(mapping, statePropertyName, key) {
289
+ // Remember what the previous key was so that key changes can be detected when data is being loaded from Onyx. This will allow
290
+ // dependent keys to finish loading their data.
291
+ // eslint-disable-next-line no-param-reassign
292
+ mapOnyxToState[statePropertyName].previousKey = key;
267
293
 
268
294
  // eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs
269
295
  this.activeConnectionIDs[key] = Onyx.connect({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "1.0.110",
3
+ "version": "1.0.112",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",