Skip to content

Commit b7c84fb

Browse files
committed
fix: simplify fastMerge
1 parent 351cc72 commit b7c84fb

File tree

3 files changed

+26
-48
lines changed

3 files changed

+26
-48
lines changed

lib/OnyxUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1268,7 +1268,7 @@ function mergeChanges<TValue extends OnyxInput<OnyxKey> | undefined, TChange ext
12681268
// Object values are then merged one after the other
12691269
return changes.reduce<FastMergeResult<TChange>>(
12701270
(modifiedData, change) => {
1271-
const fastMergeResult = utils.fastMerge(modifiedData.result, change, {isBatchingMergeChanges: true});
1271+
const fastMergeResult = utils.fastMerge(modifiedData.result, change, {shouldMarkRemovedObjects: true});
12721272
// eslint-disable-next-line no-param-reassign
12731273
modifiedData.result = fastMergeResult.result;
12741274
// eslint-disable-next-line no-param-reassign

lib/utils.ts

Lines changed: 24 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ type FastMergeOptions = {
1212
shouldRemoveNestedNulls?: boolean;
1313

1414
/** If true, it means that we are batching merge changes before applying them to the Onyx value, so we must use a special logic to handle these changes. */
15-
isBatchingMergeChanges?: boolean;
15+
shouldMarkRemovedObjects?: boolean;
1616

1717
/** If true, any nested objects that contains the internal "ONYX_INTERNALS__REPLACE_OBJECT_MARK" flag will be completely replaced instead of merged. */
1818
shouldReplaceMarkedObjects?: boolean;
@@ -136,16 +136,33 @@ function mergeObject<TObject extends Record<string, unknown>>(
136136
}
137137

138138
const targetProperty = targetObject?.[key];
139-
const targetWithMarks = getTargetPropertyWithRemovalMark(targetProperty, sourceProperty, options, metadata, basePath);
140-
const {finalDestinationProperty, stopTraversing} = replaceMarkedObjects(sourceProperty, options);
139+
const targetPropertyWithMarks = (targetProperty ?? {}) as Record<string, unknown>;
140+
141+
// If we are batching merge changes and the previous merge change (targetValue) is null,
142+
// it means we want to fully replace this object when merging the batched changes with the Onyx value.
143+
// To achieve this, we first mark these nested objects with an internal flag. With the desired objects
144+
// marked, when calling this method again with "shouldReplaceMarkedObjects" set to true we can proceed
145+
// effectively replace them in the next condition.
146+
if (options?.shouldMarkRemovedObjects && targetProperty === null) {
147+
targetPropertyWithMarks[ONYX_INTERNALS__REPLACE_OBJECT_MARK] = true;
148+
metadata.replaceNullPatches.push([[...basePath], {...sourceProperty}]);
149+
}
150+
151+
// Later, when merging the batched changes with the Onyx value, if a nested object of the batched changes
152+
// has the internal flag set, we replace the entire destination object with the source one and remove
153+
// the flag.
154+
if (options.shouldReplaceMarkedObjects && sourceProperty[ONYX_INTERNALS__REPLACE_OBJECT_MARK]) {
155+
// We do a spread here in order to have a new object reference and allow us to delete the internal flag
156+
// of the merged object only.
157+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
158+
const {ONYX_INTERNALS__REPLACE_OBJECT_MARK: _mark, ...sourcePropertyWithoutMark} = sourceProperty;
141159

142-
if (stopTraversing) {
143-
destination[key] = finalDestinationProperty;
160+
destination[key] = sourcePropertyWithoutMark;
144161
// eslint-disable-next-line no-continue
145162
continue;
146163
}
147164

148-
destination[key] = fastMerge(targetWithMarks, sourceProperty, options, metadata, [...basePath, key]).result;
165+
destination[key] = fastMerge(targetPropertyWithMarks, sourceProperty, options, metadata, [...basePath, key]).result;
149166
}
150167

151168
return destination as TObject;
@@ -166,51 +183,12 @@ function isMergeableObject<TObject extends Record<string, unknown>>(value: unkno
166183
return isNonNullObject && !(value instanceof RegExp) && !(value instanceof Date) && !Array.isArray(value);
167184
}
168185

169-
function getTargetPropertyWithRemovalMark<TObject extends Record<string, unknown>>(
170-
targetProperty: unknown,
171-
sourceProperty: Record<string, unknown>,
172-
options: FastMergeOptions,
173-
metadata: FastMergeMetadata,
174-
basePath: string[] = [],
175-
): TObject {
176-
const targetPropertyWithMarks = (targetProperty ?? {}) as Record<string, unknown>;
177-
178-
// If we are batching merge changes and the previous merge change (targetValue) is null,
179-
// it means we want to fully replace this object when merging the batched changes with the Onyx value.
180-
// To achieve this, we first mark these nested objects with an internal flag. With the desired objects
181-
// marked, when calling this method again with "shouldReplaceMarkedObjects" set to true we can proceed
182-
// effectively replace them in the next condition.
183-
if (options?.isBatchingMergeChanges && targetProperty === null) {
184-
targetPropertyWithMarks[ONYX_INTERNALS__REPLACE_OBJECT_MARK] = true;
185-
metadata.replaceNullPatches.push([[...basePath], {...sourceProperty}]);
186-
}
187-
188-
return targetPropertyWithMarks as TObject;
189-
}
190-
191-
function replaceMarkedObjects<TObject extends Record<string, unknown>>(sourceProperty: TObject, options: FastMergeOptions): {finalDestinationProperty?: TObject; stopTraversing: boolean} {
192-
// Then, when merging the batched changes with the Onyx value, if a nested object of the batched changes
193-
// has the internal flag set, we replace the entire destination object with the source one and remove
194-
// the flag.
195-
if (options.shouldReplaceMarkedObjects && sourceProperty[ONYX_INTERNALS__REPLACE_OBJECT_MARK]) {
196-
// We do a spread here in order to have a new object reference and allow us to delete the internal flag
197-
// of the merged object only.
198-
199-
const destinationProperty = {...sourceProperty};
200-
delete destinationProperty.ONYX_INTERNALS__REPLACE_OBJECT_MARK;
201-
return {finalDestinationProperty: destinationProperty, stopTraversing: true};
202-
}
203-
204-
// For the normal situations we'll just call `fastMerge()` again to merge the nested object.
205-
return {stopTraversing: false};
206-
}
207-
208186
/** Deep removes the nested null values from the given value. */
209187
function removeNestedNullValues<TValue extends OnyxInput<OnyxKey> | null>(value: TValue): TValue {
210188
if (typeof value === 'object' && !Array.isArray(value)) {
211189
return fastMerge(value, value, {
212190
shouldRemoveNestedNulls: true,
213-
isBatchingMergeChanges: false,
191+
shouldMarkRemovedObjects: false,
214192
shouldReplaceMarkedObjects: false,
215193
}).result;
216194
}

tests/unit/fastMergeTest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ describe('fastMerge', () => {
123123
it('should add the "ONYX_INTERNALS__REPLACE_OBJECT_MARK" flag to the target object when its source is set to null and "isBatchingMergeChanges" is true', () => {
124124
const result = utils.fastMerge(testMergeChanges[1], testMergeChanges[0], {
125125
shouldRemoveNestedNulls: true,
126-
isBatchingMergeChanges: true,
126+
shouldMarkRemovedObjects: true,
127127
});
128128

129129
expect(result.result).toEqual({

0 commit comments

Comments
 (0)