react-native-onyx 2.0.140 → 3.0.1
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/API.md +2 -8
- package/README.md +2 -62
- package/dist/Onyx.d.ts +7 -13
- package/dist/Onyx.js +9 -21
- package/dist/OnyxConnectionManager.d.ts +1 -1
- package/dist/OnyxConnectionManager.js +14 -22
- package/dist/OnyxUtils.d.ts +19 -13
- package/dist/OnyxUtils.js +76 -247
- package/dist/index.d.ts +2 -4
- package/dist/index.js +1 -3
- package/dist/types.d.ts +6 -75
- package/dist/useOnyx.js +0 -1
- package/dist/utils.d.ts +1 -6
- package/dist/utils.js +0 -7
- package/package.json +1 -1
- package/dist/PerformanceUtils.d.ts +0 -8
- package/dist/PerformanceUtils.js +0 -52
- package/dist/withOnyx/index.d.ts +0 -15
- package/dist/withOnyx/index.js +0 -322
- package/dist/withOnyx/types.d.ts +0 -129
- package/dist/withOnyx/types.js +0 -2
package/dist/useOnyx.js
CHANGED
|
@@ -152,7 +152,6 @@ function useOnyx(key, options, dependencies = []) {
|
|
|
152
152
|
onStoreChangeFnRef.current();
|
|
153
153
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
154
154
|
}, [...dependencies]);
|
|
155
|
-
// Mimics withOnyx's checkEvictableKeys() behavior.
|
|
156
155
|
const checkEvictableKey = (0, react_1.useCallback)(() => {
|
|
157
156
|
if ((options === null || options === void 0 ? void 0 : options.canEvict) === undefined || !connectionRef.current) {
|
|
158
157
|
return;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OnyxInput, OnyxKey } from './types';
|
|
2
2
|
type EmptyObject = Record<string, never>;
|
|
3
3
|
type EmptyValue = EmptyObject | null | undefined;
|
|
4
4
|
/**
|
|
@@ -63,10 +63,6 @@ declare function pick<TValue>(obj: Record<string, TValue>, condition: string | s
|
|
|
63
63
|
* @returns The object containing only the remaining entries after omission.
|
|
64
64
|
*/
|
|
65
65
|
declare function omit<TValue>(obj: Record<string, TValue>, condition: string | string[] | ((entry: [string, TValue]) => boolean)): Record<string, TValue>;
|
|
66
|
-
/**
|
|
67
|
-
* Whether the connect options has the `withOnyxInstance` property defined, that is, it's used by the `withOnyx()` HOC.
|
|
68
|
-
*/
|
|
69
|
-
declare function hasWithOnyxInstance<TKey extends OnyxKey>(mapping: ConnectOptions<TKey>): unknown;
|
|
70
66
|
declare const _default: {
|
|
71
67
|
fastMerge: typeof fastMerge;
|
|
72
68
|
isEmptyObject: typeof isEmptyObject;
|
|
@@ -75,7 +71,6 @@ declare const _default: {
|
|
|
75
71
|
checkCompatibilityWithExistingValue: typeof checkCompatibilityWithExistingValue;
|
|
76
72
|
pick: typeof pick;
|
|
77
73
|
omit: typeof omit;
|
|
78
|
-
hasWithOnyxInstance: typeof hasWithOnyxInstance;
|
|
79
74
|
ONYX_INTERNALS__REPLACE_OBJECT_MARK: string;
|
|
80
75
|
};
|
|
81
76
|
export default _default;
|
package/dist/utils.js
CHANGED
|
@@ -204,12 +204,6 @@ function pick(obj, condition) {
|
|
|
204
204
|
function omit(obj, condition) {
|
|
205
205
|
return filterObject(obj, condition, false);
|
|
206
206
|
}
|
|
207
|
-
/**
|
|
208
|
-
* Whether the connect options has the `withOnyxInstance` property defined, that is, it's used by the `withOnyx()` HOC.
|
|
209
|
-
*/
|
|
210
|
-
function hasWithOnyxInstance(mapping) {
|
|
211
|
-
return 'withOnyxInstance' in mapping && mapping.withOnyxInstance;
|
|
212
|
-
}
|
|
213
207
|
exports.default = {
|
|
214
208
|
fastMerge,
|
|
215
209
|
isEmptyObject,
|
|
@@ -218,6 +212,5 @@ exports.default = {
|
|
|
218
212
|
checkCompatibilityWithExistingValue,
|
|
219
213
|
pick,
|
|
220
214
|
omit,
|
|
221
|
-
hasWithOnyxInstance,
|
|
222
215
|
ONYX_INTERNALS__REPLACE_OBJECT_MARK,
|
|
223
216
|
};
|
package/package.json
CHANGED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { OnyxKey } from './types';
|
|
2
|
-
import type { Mapping } from './Onyx';
|
|
3
|
-
declare function setShouldDebugSetState(debug: boolean): void;
|
|
4
|
-
/**
|
|
5
|
-
* Provide insights into why a setState() call occurred by diffing the before and after values.
|
|
6
|
-
*/
|
|
7
|
-
declare function logSetStateCall<TKey extends OnyxKey>(mapping: Mapping<TKey>, previousValue: unknown, newValue: unknown, caller: string, keyThatChanged?: string): void;
|
|
8
|
-
export { logSetStateCall, setShouldDebugSetState };
|
package/dist/PerformanceUtils.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.logSetStateCall = logSetStateCall;
|
|
7
|
-
exports.setShouldDebugSetState = setShouldDebugSetState;
|
|
8
|
-
const transform_1 = __importDefault(require("lodash/transform"));
|
|
9
|
-
const fast_equals_1 = require("fast-equals");
|
|
10
|
-
let debugSetState = false;
|
|
11
|
-
function setShouldDebugSetState(debug) {
|
|
12
|
-
debugSetState = debug;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Deep diff between two objects. Useful for figuring out what changed about an object from one render to the next so
|
|
16
|
-
* that state and props updates can be optimized.
|
|
17
|
-
*/
|
|
18
|
-
function diffObject(object, base) {
|
|
19
|
-
return (0, transform_1.default)(object, (result, value, key) => {
|
|
20
|
-
if ((0, fast_equals_1.deepEqual)(value, base[key])) {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
if (typeof value === 'object' && typeof base[key] === 'object') {
|
|
24
|
-
// eslint-disable-next-line no-param-reassign
|
|
25
|
-
result[key] = diffObject(value, base[key]);
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
// eslint-disable-next-line no-param-reassign
|
|
29
|
-
result[key] = value;
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Provide insights into why a setState() call occurred by diffing the before and after values.
|
|
35
|
-
*/
|
|
36
|
-
function logSetStateCall(mapping, previousValue, newValue, caller, keyThatChanged) {
|
|
37
|
-
if (!debugSetState) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const logParams = {};
|
|
41
|
-
if (keyThatChanged) {
|
|
42
|
-
logParams.keyThatChanged = keyThatChanged;
|
|
43
|
-
}
|
|
44
|
-
if (newValue && previousValue && typeof newValue === 'object' && typeof previousValue === 'object') {
|
|
45
|
-
logParams.difference = diffObject(previousValue, newValue);
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
logParams.previousValue = previousValue;
|
|
49
|
-
logParams.newValue = newValue;
|
|
50
|
-
}
|
|
51
|
-
console.debug(`[Onyx-Debug] ${'displayName' in mapping && mapping.displayName} setState() called. Subscribed to key '${mapping.key}' (${caller})`, logParams);
|
|
52
|
-
}
|
package/dist/withOnyx/index.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This is a higher order component that provides the ability to map a state property directly to
|
|
3
|
-
* something in Onyx (a key/value store). That way, as soon as data in Onyx changes, the state will be set and the view
|
|
4
|
-
* will automatically change to reflect the new data.
|
|
5
|
-
*/
|
|
6
|
-
import React from 'react';
|
|
7
|
-
import type { MapOnyxToState } from './types';
|
|
8
|
-
/**
|
|
9
|
-
* @deprecated Use `useOnyx` instead of `withOnyx` whenever possible.
|
|
10
|
-
*
|
|
11
|
-
* This is a higher order component that provides the ability to map a state property directly to
|
|
12
|
-
* something in Onyx (a key/value store). That way, as soon as data in Onyx changes, the state will be set and the view
|
|
13
|
-
* will automatically change to reflect the new data.
|
|
14
|
-
*/
|
|
15
|
-
export default function <TComponentProps, TOnyxProps>(mapOnyxToState: MapOnyxToState<TComponentProps, TOnyxProps>, shouldDelayUpdates?: boolean): (component: React.ComponentType<TComponentProps>) => React.ComponentType<Omit<TComponentProps, keyof TOnyxProps>>;
|
package/dist/withOnyx/index.js
DELETED
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.default = default_1;
|
|
40
|
-
/**
|
|
41
|
-
* This is a higher order component that provides the ability to map a state property directly to
|
|
42
|
-
* something in Onyx (a key/value store). That way, as soon as data in Onyx changes, the state will be set and the view
|
|
43
|
-
* will automatically change to reflect the new data.
|
|
44
|
-
*/
|
|
45
|
-
const react_1 = __importDefault(require("react"));
|
|
46
|
-
const OnyxUtils_1 = __importDefault(require("../OnyxUtils"));
|
|
47
|
-
const Str = __importStar(require("../Str"));
|
|
48
|
-
const utils_1 = __importDefault(require("../utils"));
|
|
49
|
-
const OnyxCache_1 = __importDefault(require("../OnyxCache"));
|
|
50
|
-
const OnyxConnectionManager_1 = __importDefault(require("../OnyxConnectionManager"));
|
|
51
|
-
// 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
|
|
52
|
-
// if a key has changed, it's a good idea to skip looking at these properties since they would have unexpected results.
|
|
53
|
-
const mappingPropertiesToIgnoreChangesTo = ['allowStaleData'];
|
|
54
|
-
/**
|
|
55
|
-
* Returns the display name of a component
|
|
56
|
-
*/
|
|
57
|
-
function getDisplayName(component) {
|
|
58
|
-
return component.displayName || component.name || 'Component';
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Removes all the keys from state that are unrelated to the onyx data being mapped to the component.
|
|
62
|
-
*
|
|
63
|
-
* @param state of the component
|
|
64
|
-
* @param onyxToStateMapping the object holding all of the mapping configuration for the component
|
|
65
|
-
*/
|
|
66
|
-
function getOnyxDataFromState(state, onyxToStateMapping) {
|
|
67
|
-
return utils_1.default.pick(state, Object.keys(onyxToStateMapping));
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Utility function to return the properly typed entries of the `withOnyx` mapping object.
|
|
71
|
-
*/
|
|
72
|
-
function mapOnyxToStateEntries(mapOnyxToState) {
|
|
73
|
-
return Object.entries(mapOnyxToState);
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* @deprecated Use `useOnyx` instead of `withOnyx` whenever possible.
|
|
77
|
-
*
|
|
78
|
-
* This is a higher order component that provides the ability to map a state property directly to
|
|
79
|
-
* something in Onyx (a key/value store). That way, as soon as data in Onyx changes, the state will be set and the view
|
|
80
|
-
* will automatically change to reflect the new data.
|
|
81
|
-
*/
|
|
82
|
-
function default_1(mapOnyxToState, shouldDelayUpdates = false) {
|
|
83
|
-
// A list of keys that must be present in tempState before we can render the WrappedComponent
|
|
84
|
-
const requiredKeysForInit = Object.keys(utils_1.default.omit(mapOnyxToState, ([, options]) => options.initWithStoredValues === false));
|
|
85
|
-
return (WrappedComponent) => {
|
|
86
|
-
const displayName = getDisplayName(WrappedComponent);
|
|
87
|
-
class withOnyx extends react_1.default.Component {
|
|
88
|
-
constructor(props) {
|
|
89
|
-
super(props);
|
|
90
|
-
this.pendingSetStates = [];
|
|
91
|
-
this.shouldDelayUpdates = shouldDelayUpdates;
|
|
92
|
-
this.setWithOnyxState = this.setWithOnyxState.bind(this);
|
|
93
|
-
this.flushPendingSetStates = this.flushPendingSetStates.bind(this);
|
|
94
|
-
// This stores all the Onyx connections to be used when the component unmounts so everything can be
|
|
95
|
-
// disconnected. It is a key value store with the format {[mapping.key]: connection metadata object}.
|
|
96
|
-
this.activeConnections = {};
|
|
97
|
-
const cachedState = mapOnyxToStateEntries(mapOnyxToState).reduce((resultObj, [propName, mapping]) => {
|
|
98
|
-
const key = Str.result(mapping.key, props);
|
|
99
|
-
const value = OnyxUtils_1.default.tryGetCachedValue(key, mapping);
|
|
100
|
-
const hasCacheForKey = OnyxCache_1.default.hasCacheForKey(key);
|
|
101
|
-
/**
|
|
102
|
-
* 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.
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
*
|
|
106
|
-
* Onyx.merge('report_123', value);
|
|
107
|
-
* Navigation.navigate(route); // Where "route" expects the "value" to be available immediately once rendered.
|
|
108
|
-
*
|
|
109
|
-
* 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).
|
|
110
|
-
* So, we won't use the cache optimization here as it will lead us to arbitrarily defer various actions in the application code.
|
|
111
|
-
*/
|
|
112
|
-
const hasPendingMergeForKey = OnyxUtils_1.default.hasPendingMergeForKey(key);
|
|
113
|
-
const hasValueInCache = hasCacheForKey || value !== undefined;
|
|
114
|
-
const shouldSetState = mapping.initWithStoredValues !== false && ((hasValueInCache && !hasPendingMergeForKey) || !!mapping.allowStaleData);
|
|
115
|
-
if (shouldSetState) {
|
|
116
|
-
// eslint-disable-next-line no-param-reassign
|
|
117
|
-
resultObj[propName] = value;
|
|
118
|
-
}
|
|
119
|
-
return resultObj;
|
|
120
|
-
}, {});
|
|
121
|
-
// If we have all the data we need, then we can render the component immediately
|
|
122
|
-
cachedState.loading = Object.keys(cachedState).length < requiredKeysForInit.length;
|
|
123
|
-
// Object holding the temporary initial state for the component while we load the various Onyx keys
|
|
124
|
-
this.tempState = cachedState;
|
|
125
|
-
this.state = cachedState;
|
|
126
|
-
}
|
|
127
|
-
componentDidMount() {
|
|
128
|
-
const onyxDataFromState = getOnyxDataFromState(this.state, mapOnyxToState);
|
|
129
|
-
// Subscribe each of the state properties to the proper Onyx key
|
|
130
|
-
mapOnyxToStateEntries(mapOnyxToState).forEach(([propName, mapping]) => {
|
|
131
|
-
if (mappingPropertiesToIgnoreChangesTo.includes(propName)) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const key = Str.result(mapping.key, Object.assign(Object.assign({}, this.props), onyxDataFromState));
|
|
135
|
-
this.connectMappingToOnyx(mapping, propName, key);
|
|
136
|
-
});
|
|
137
|
-
this.checkEvictableKeys();
|
|
138
|
-
}
|
|
139
|
-
componentDidUpdate(prevProps, prevState) {
|
|
140
|
-
// 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
|
|
141
|
-
// key and connected to the new key.
|
|
142
|
-
// For example, a key could change if KeyB depends on data loading from Onyx for KeyA.
|
|
143
|
-
const isFirstTimeUpdatingAfterLoading = prevState.loading && !this.state.loading;
|
|
144
|
-
const onyxDataFromState = getOnyxDataFromState(this.state, mapOnyxToState);
|
|
145
|
-
const prevOnyxDataFromState = getOnyxDataFromState(prevState, mapOnyxToState);
|
|
146
|
-
mapOnyxToStateEntries(mapOnyxToState).forEach(([propName, mapping]) => {
|
|
147
|
-
// Some properties can be ignored because they aren't related to onyx keys and they will never change
|
|
148
|
-
if (mappingPropertiesToIgnoreChangesTo.includes(propName)) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
// The previous key comes from either:
|
|
152
|
-
// 1) The initial key that was connected to (ie. set from `componentDidMount()`)
|
|
153
|
-
// 2) The updated props which caused `componentDidUpdate()` to run
|
|
154
|
-
// The first case cannot be used all the time because of race conditions where `componentDidUpdate()` can be triggered before connectingMappingToOnyx() is done
|
|
155
|
-
// (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.
|
|
156
|
-
// 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,
|
|
157
|
-
// 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.
|
|
158
|
-
const previousKey = isFirstTimeUpdatingAfterLoading ? mapping.previousKey : Str.result(mapping.key, Object.assign(Object.assign({}, prevProps), prevOnyxDataFromState));
|
|
159
|
-
const newKey = Str.result(mapping.key, Object.assign(Object.assign({}, this.props), onyxDataFromState));
|
|
160
|
-
if (previousKey !== newKey) {
|
|
161
|
-
OnyxConnectionManager_1.default.disconnect(this.activeConnections[previousKey]);
|
|
162
|
-
delete this.activeConnections[previousKey];
|
|
163
|
-
this.connectMappingToOnyx(mapping, propName, newKey);
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
this.checkEvictableKeys();
|
|
167
|
-
}
|
|
168
|
-
componentWillUnmount() {
|
|
169
|
-
// Disconnect everything from Onyx
|
|
170
|
-
mapOnyxToStateEntries(mapOnyxToState).forEach(([, mapping]) => {
|
|
171
|
-
const key = Str.result(mapping.key, Object.assign(Object.assign({}, this.props), getOnyxDataFromState(this.state, mapOnyxToState)));
|
|
172
|
-
OnyxConnectionManager_1.default.disconnect(this.activeConnections[key]);
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
setStateProxy(modifier) {
|
|
176
|
-
if (this.shouldDelayUpdates) {
|
|
177
|
-
this.pendingSetStates.push(modifier);
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
this.setState(modifier);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* This method is used by the internal raw Onyx `sendDataToConnection`, it is designed to prevent unnecessary renders while a component
|
|
185
|
-
* still in a "loading" (read "mounting") state. The temporary initial state is saved to the HOC instance and setState()
|
|
186
|
-
* only called once all the necessary data has been collected.
|
|
187
|
-
*
|
|
188
|
-
* There is however the possibility the component could have been updated by a call to setState()
|
|
189
|
-
* before the data was "initially" collected. A race condition.
|
|
190
|
-
* For example some update happened on some key, while onyx was still gathering the initial hydration data.
|
|
191
|
-
* This update is disptached directly to setStateProxy and therefore the component has the most up-to-date data
|
|
192
|
-
*
|
|
193
|
-
* This is a design flaw in Onyx itself as dispatching updates before initial hydration is not a correct event flow.
|
|
194
|
-
* We however need to workaround this issue in the HOC. The addition of initialValue makes things even more complex,
|
|
195
|
-
* since you cannot be really sure if the component has been updated before or after the initial hydration. Therefore if
|
|
196
|
-
* initialValue is there, we just check if the update is different than that and then try to handle it as best as we can.
|
|
197
|
-
*/
|
|
198
|
-
setWithOnyxState(statePropertyName, val) {
|
|
199
|
-
const prevVal = this.state[statePropertyName];
|
|
200
|
-
// If the component is not loading (read "mounting"), then we can just update the state
|
|
201
|
-
// There is a small race condition.
|
|
202
|
-
// When calling setWithOnyxState we delete the tempState object that is used to hold temporary state updates while the HOC is gathering data.
|
|
203
|
-
// However the loading flag is only set on the setState callback down below. setState however is an async operation that is also batched,
|
|
204
|
-
// therefore there is a small window of time where the loading flag is not false but the tempState is already gone
|
|
205
|
-
// (while the update is queued and waiting to be applied).
|
|
206
|
-
// This simply bypasses the loading check if the tempState is gone and the update can be safely queued with a normal setStateProxy.
|
|
207
|
-
if (!this.state.loading || !this.tempState) {
|
|
208
|
-
// Performance optimization, do not trigger update with same values
|
|
209
|
-
if (prevVal === val || (utils_1.default.isEmptyObject(prevVal) && utils_1.default.isEmptyObject(val))) {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
const valueWithoutNull = val === null ? undefined : val;
|
|
213
|
-
this.setStateProxy({ [statePropertyName]: valueWithoutNull });
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
this.tempState[statePropertyName] = val;
|
|
217
|
-
// If some key does not have a value yet, do not update the state yet
|
|
218
|
-
const tempStateIsMissingKey = requiredKeysForInit.some((key) => { var _a; return !(key in ((_a = this.tempState) !== null && _a !== void 0 ? _a : {})); });
|
|
219
|
-
if (tempStateIsMissingKey) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
const stateUpdate = Object.assign({}, this.tempState);
|
|
223
|
-
delete this.tempState;
|
|
224
|
-
// Full of hacky workarounds to prevent the race condition described above.
|
|
225
|
-
this.setState((prevState) => {
|
|
226
|
-
const finalState = Object.keys(stateUpdate).reduce((result, _key) => {
|
|
227
|
-
const key = _key;
|
|
228
|
-
if (key === 'loading') {
|
|
229
|
-
return result;
|
|
230
|
-
}
|
|
231
|
-
// if value is already there then we can discard the value we are trying to hydrate
|
|
232
|
-
if (prevState[key] !== undefined && prevState[key] !== null) {
|
|
233
|
-
// eslint-disable-next-line no-param-reassign
|
|
234
|
-
result[key] = prevState[key];
|
|
235
|
-
}
|
|
236
|
-
else if (stateUpdate[key] !== null) {
|
|
237
|
-
// eslint-disable-next-line no-param-reassign
|
|
238
|
-
result[key] = stateUpdate[key];
|
|
239
|
-
}
|
|
240
|
-
return result;
|
|
241
|
-
}, {});
|
|
242
|
-
finalState.loading = false;
|
|
243
|
-
return finalState;
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Makes sure each Onyx key we requested has been set to state with a value of some kind.
|
|
248
|
-
* We are doing this so that the wrapped component will only render when all the data
|
|
249
|
-
* it needs is available to it.
|
|
250
|
-
*/
|
|
251
|
-
checkEvictableKeys() {
|
|
252
|
-
// We will add this key to our list of recently accessed keys
|
|
253
|
-
// if the canEvict function returns true. This is necessary criteria
|
|
254
|
-
// we MUST use to specify if a key can be removed or not.
|
|
255
|
-
mapOnyxToStateEntries(mapOnyxToState).forEach(([, mapping]) => {
|
|
256
|
-
if (mapping.canEvict === undefined) {
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
const canEvict = !!Str.result(mapping.canEvict, this.props);
|
|
260
|
-
const key = Str.result(mapping.key, this.props);
|
|
261
|
-
if (!OnyxCache_1.default.isEvictableKey(key)) {
|
|
262
|
-
throw new Error(`canEvict can't be used on key '${key}'. This key must explicitly be flagged as safe for removal by adding it to Onyx.init({evictableKeys: []}).`);
|
|
263
|
-
}
|
|
264
|
-
if (canEvict) {
|
|
265
|
-
OnyxConnectionManager_1.default.removeFromEvictionBlockList(this.activeConnections[key]);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
OnyxConnectionManager_1.default.addToEvictionBlockList(this.activeConnections[key]);
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Takes a single mapping and binds the state of the component to the store
|
|
274
|
-
*
|
|
275
|
-
* @param mapping.key key to connect to. can be a string or a
|
|
276
|
-
* function that takes this.props as an argument and returns a string
|
|
277
|
-
* @param statePropertyName the name of the state property that Onyx will add the data to
|
|
278
|
-
* @param [mapping.initWithStoredValues] If set to false, then no data will be prefilled into the
|
|
279
|
-
* component
|
|
280
|
-
* @param key to connect to Onyx with
|
|
281
|
-
*/
|
|
282
|
-
connectMappingToOnyx(mapping, statePropertyName, key) {
|
|
283
|
-
const onyxMapping = mapOnyxToState[statePropertyName];
|
|
284
|
-
// Remember what the previous key was so that key changes can be detected when data is being loaded from Onyx. This will allow
|
|
285
|
-
// dependent keys to finish loading their data.
|
|
286
|
-
// eslint-disable-next-line no-param-reassign
|
|
287
|
-
onyxMapping.previousKey = key;
|
|
288
|
-
// eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs
|
|
289
|
-
this.activeConnections[key] = OnyxConnectionManager_1.default.connect(Object.assign(Object.assign({}, mapping), { key, statePropertyName: statePropertyName, withOnyxInstance: this, displayName }));
|
|
290
|
-
}
|
|
291
|
-
flushPendingSetStates() {
|
|
292
|
-
if (!this.shouldDelayUpdates) {
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
this.shouldDelayUpdates = false;
|
|
296
|
-
this.pendingSetStates.forEach((modifier) => {
|
|
297
|
-
this.setState(modifier);
|
|
298
|
-
});
|
|
299
|
-
this.pendingSetStates = [];
|
|
300
|
-
}
|
|
301
|
-
render() {
|
|
302
|
-
// Remove any null values so that React replaces them with default props
|
|
303
|
-
const propsToPass = utils_1.default.omit(this.props, ([, propValue]) => propValue === null);
|
|
304
|
-
if (this.state.loading) {
|
|
305
|
-
return null;
|
|
306
|
-
}
|
|
307
|
-
// Remove any internal state properties used by withOnyx
|
|
308
|
-
// that should not be passed to a wrapped component
|
|
309
|
-
const stateToPass = utils_1.default.omit(this.state, ([stateKey, stateValue]) => stateKey === 'loading' || stateValue === null);
|
|
310
|
-
// Spreading props and state is necessary in an HOC where the data cannot be predicted
|
|
311
|
-
return (react_1.default.createElement(WrappedComponent, Object.assign({ markReadyForHydration: this.flushPendingSetStates }, propsToPass, stateToPass, { ref: this.props.forwardedRef })));
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
withOnyx.displayName = `withOnyx(${displayName})`;
|
|
315
|
-
return react_1.default.forwardRef((props, ref) => {
|
|
316
|
-
const Component = withOnyx;
|
|
317
|
-
return (react_1.default.createElement(Component
|
|
318
|
-
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
319
|
-
, Object.assign({}, props, { forwardedRef: ref })));
|
|
320
|
-
});
|
|
321
|
-
};
|
|
322
|
-
}
|
package/dist/withOnyx/types.d.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import type { ForwardedRef } from 'react';
|
|
2
|
-
import type { IsEqual } from 'type-fest';
|
|
3
|
-
import type { CollectionKeyBase, ExtractOnyxCollectionValue, KeyValueMapping, OnyxCollection, OnyxEntry, OnyxKey, OnyxValue, Selector } from '../types';
|
|
4
|
-
/**
|
|
5
|
-
* Represents the base mapping options between an Onyx key and the component's prop.
|
|
6
|
-
*/
|
|
7
|
-
type BaseMapping<TComponentProps, TOnyxProps> = {
|
|
8
|
-
canEvict?: boolean | ((props: Omit<TComponentProps, keyof TOnyxProps>) => boolean);
|
|
9
|
-
initWithStoredValues?: boolean;
|
|
10
|
-
allowStaleData?: boolean;
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Represents the string / function `key` mapping option between an Onyx key and the component's prop.
|
|
14
|
-
*
|
|
15
|
-
* If `key` is `string`, the type of the Onyx value that is associated with `key` must match with the type of the component's prop,
|
|
16
|
-
* otherwise an error will be thrown.
|
|
17
|
-
*
|
|
18
|
-
* If `key` is `function`, the return type of `key` function must be a valid Onyx key and the type of the Onyx value associated
|
|
19
|
-
* with `key` must match with the type of the component's prop, otherwise an error will be thrown.
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```ts
|
|
23
|
-
* // Onyx prop with `string` key
|
|
24
|
-
* onyxProp: {
|
|
25
|
-
* key: ONYXKEYS.ACCOUNT,
|
|
26
|
-
* },
|
|
27
|
-
*
|
|
28
|
-
* // Onyx prop with `function` key
|
|
29
|
-
* onyxProp: {
|
|
30
|
-
* key: ({reportId}) => ONYXKEYS.ACCOUNT,
|
|
31
|
-
* },
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
type BaseMappingKey<TComponentProps, TOnyxProps, TOnyxProp extends keyof TOnyxProps, TOnyxKey extends OnyxKey, TOnyxValue> = IsEqual<TOnyxValue, TOnyxProps[TOnyxProp]> extends true ? {
|
|
35
|
-
key: TOnyxKey | ((props: Omit<TComponentProps, keyof TOnyxProps> & Partial<TOnyxProps>) => TOnyxKey);
|
|
36
|
-
} : never;
|
|
37
|
-
/**
|
|
38
|
-
* Represents the string `key` and `selector` mapping options between an Onyx key and the component's prop.
|
|
39
|
-
*
|
|
40
|
-
* The function signature and return type of `selector` must match with the type of the component's prop,
|
|
41
|
-
* otherwise an error will be thrown.
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```ts
|
|
45
|
-
* // Onyx prop with `string` key and selector
|
|
46
|
-
* onyxProp: {
|
|
47
|
-
* key: ONYXKEYS.ACCOUNT,
|
|
48
|
-
* selector: (value: Account | null): string => value?.id ?? '',
|
|
49
|
-
* },
|
|
50
|
-
* ```
|
|
51
|
-
*/
|
|
52
|
-
type BaseMappingStringKeyAndSelector<TComponentProps, TOnyxProps, TReturnType, TOnyxKey extends OnyxKey> = {
|
|
53
|
-
key: TOnyxKey;
|
|
54
|
-
selector: Selector<TOnyxKey, TOnyxProps, TReturnType>;
|
|
55
|
-
};
|
|
56
|
-
/**
|
|
57
|
-
* Represents the function `key` and `selector` mapping options between an Onyx key and the component's prop.
|
|
58
|
-
*
|
|
59
|
-
* The function signature and return type of `selector` must match with the type of the component's prop,
|
|
60
|
-
* otherwise an error will be thrown.
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* ```ts
|
|
64
|
-
* // Onyx prop with `function` key and selector
|
|
65
|
-
* onyxProp: {
|
|
66
|
-
* key: ({reportId}) => ONYXKEYS.ACCOUNT,
|
|
67
|
-
* selector: (value: Account | null) => value?.id ?? '',
|
|
68
|
-
* },
|
|
69
|
-
* ```
|
|
70
|
-
*/
|
|
71
|
-
type BaseMappingFunctionKeyAndSelector<TComponentProps, TOnyxProps, TReturnType, TOnyxKey extends OnyxKey> = {
|
|
72
|
-
key: (props: Omit<TComponentProps, keyof TOnyxProps> & Partial<TOnyxProps>) => TOnyxKey;
|
|
73
|
-
selector: Selector<TOnyxKey, TOnyxProps, TReturnType>;
|
|
74
|
-
};
|
|
75
|
-
/**
|
|
76
|
-
* Represents the mapping options between an Onyx key and the component's prop with all its possibilities.
|
|
77
|
-
*/
|
|
78
|
-
type Mapping<TComponentProps, TOnyxProps, TOnyxProp extends keyof TOnyxProps, TOnyxKey extends OnyxKey> = BaseMapping<TComponentProps, TOnyxProps> & (BaseMappingKey<TComponentProps, TOnyxProps, TOnyxProp, TOnyxKey, OnyxEntry<KeyValueMapping[TOnyxKey]>> | BaseMappingStringKeyAndSelector<TComponentProps, TOnyxProps, TOnyxProps[TOnyxProp], TOnyxKey> | BaseMappingFunctionKeyAndSelector<TComponentProps, TOnyxProps, TOnyxProps[TOnyxProp], TOnyxKey>);
|
|
79
|
-
/**
|
|
80
|
-
* Represents a superset of `Mapping` type with internal properties included.
|
|
81
|
-
*/
|
|
82
|
-
type WithOnyxMapping<TComponentProps, TOnyxProps> = Mapping<TComponentProps, TOnyxProps, keyof TOnyxProps, OnyxKey> & {
|
|
83
|
-
subscriptionID: number;
|
|
84
|
-
previousKey?: OnyxKey;
|
|
85
|
-
};
|
|
86
|
-
/**
|
|
87
|
-
* Represents the mapping options between an Onyx collection key without suffix and the component's prop with all its possibilities.
|
|
88
|
-
*/
|
|
89
|
-
type CollectionMapping<TComponentProps, TOnyxProps, TOnyxProp extends keyof TOnyxProps, TOnyxKey extends CollectionKeyBase> = BaseMapping<TComponentProps, TOnyxProps> & (BaseMappingKey<TComponentProps, TOnyxProps, TOnyxProp, TOnyxKey, OnyxCollection<KeyValueMapping[TOnyxKey]>> | BaseMappingStringKeyAndSelector<TComponentProps, TOnyxProps, ExtractOnyxCollectionValue<TOnyxProps[TOnyxProp]>, TOnyxKey> | BaseMappingFunctionKeyAndSelector<TComponentProps, TOnyxProps, ExtractOnyxCollectionValue<TOnyxProps[TOnyxProp]>, TOnyxKey>);
|
|
90
|
-
/**
|
|
91
|
-
* Represents an union type of all the possible Onyx key mappings.
|
|
92
|
-
* Each `OnyxPropMapping` will be associated with its respective Onyx key, ensuring different type-safety for each object.
|
|
93
|
-
*/
|
|
94
|
-
type OnyxPropMapping<TComponentProps, TOnyxProps, TOnyxProp extends keyof TOnyxProps> = {
|
|
95
|
-
[TOnyxKey in OnyxKey]: Mapping<TComponentProps, TOnyxProps, TOnyxProp, TOnyxKey>;
|
|
96
|
-
}[OnyxKey];
|
|
97
|
-
/**
|
|
98
|
-
* Represents an union type of all the possible Onyx collection keys without suffix mappings.
|
|
99
|
-
* Each `OnyxPropCollectionMapping` will be associated with its respective Onyx key, ensuring different type-safety for each object.
|
|
100
|
-
*/
|
|
101
|
-
type OnyxPropCollectionMapping<TComponentProps, TOnyxProps, TOnyxProp extends keyof TOnyxProps> = {
|
|
102
|
-
[TOnyxKey in CollectionKeyBase]: CollectionMapping<TComponentProps, TOnyxProps, TOnyxProp, TOnyxKey>;
|
|
103
|
-
}[CollectionKeyBase];
|
|
104
|
-
/**
|
|
105
|
-
* Represents an Onyx mapping object that connects Onyx keys to component's props.
|
|
106
|
-
*/
|
|
107
|
-
type MapOnyxToState<TComponentProps, TOnyxProps> = {
|
|
108
|
-
[TOnyxProp in keyof TOnyxProps]: OnyxPropMapping<TComponentProps, TOnyxProps, TOnyxProp> | OnyxPropCollectionMapping<TComponentProps, TOnyxProps, TOnyxProp>;
|
|
109
|
-
};
|
|
110
|
-
/**
|
|
111
|
-
* Represents the `withOnyx` internal component props.
|
|
112
|
-
*/
|
|
113
|
-
type WithOnyxProps<TComponentProps, TOnyxProps> = Omit<TComponentProps, keyof TOnyxProps> & {
|
|
114
|
-
forwardedRef?: ForwardedRef<unknown>;
|
|
115
|
-
};
|
|
116
|
-
/**
|
|
117
|
-
* Represents the `withOnyx` internal component state.
|
|
118
|
-
*/
|
|
119
|
-
type WithOnyxState<TOnyxProps> = TOnyxProps & {
|
|
120
|
-
loading: boolean;
|
|
121
|
-
};
|
|
122
|
-
/**
|
|
123
|
-
* Represents the `withOnyx` internal component instance.
|
|
124
|
-
*/
|
|
125
|
-
type WithOnyxInstance = React.Component<unknown, WithOnyxState<KeyValueMapping>> & {
|
|
126
|
-
setStateProxy: (modifier: Record<string, OnyxCollection<KeyValueMapping[OnyxKey]>> | ((state: Record<string, OnyxCollection<KeyValueMapping[OnyxKey]>>) => OnyxValue<OnyxKey>)) => void;
|
|
127
|
-
setWithOnyxState: (statePropertyName: OnyxKey, value: OnyxValue<OnyxKey>) => void;
|
|
128
|
-
};
|
|
129
|
-
export type { WithOnyxMapping, MapOnyxToState, WithOnyxProps, WithOnyxInstance, WithOnyxState };
|
package/dist/withOnyx/types.js
DELETED