Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(graphql): ensure readNormalized correctly merges Map<String, dynamic> patches #1474

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

nozomemein
Copy link

@nozomemein nozomemein commented Dec 17, 2024

Thank you for the awesome package!!

This PR aims to resolve the issue in readNormalized function.
The detail is described below.

Let me know if any additional tests or changes are needed. 🚀
Thank you!

Steps to reproduce

In certain cases, patchData from optimisticResult contains Object? data, which is parsed as Map<String, dynamic>:

print(patchData["__typename"].runtimeType); // => String
print(patchData["areas"].runtimeType); // => List<Object?>
print(patchData.runtimeType);          // => _Map<String, dynamic>

Under these conditions, the patchData ends up replacing the normalized value that should be merged from the store data.

Map<String, dynamic>? readNormalized(
  String rootId, {
  bool? optimistic = true,
}) {
  var value = store.get(rootId);

  // ....
  for (final patch in optimisticPatches) {
    if (patch.data.containsKey(rootId)) {
      final patchData = patch.data[rootId];
      if (value is Map<String, Object> && patchData is Map<String, Object>) {
        value = deeplyMergeLeft([
          value,
          patchData,
        ]);
      } else {
        // It enters here and overwrites the value
        value = patchData;
      }
    }
  }

  return value;
}

The type check value is Map<String, Object> and patchData is Map<String, Object> fails to account for Map<String, dynamic>. This causes the merge to be skipped, and the existing store value is replaced instead of being merged, which results in inconsistency.


The deeplyMergeLeft function already accepts Map<String, dynamic>:

Map<String, dynamic>? deeplyMergeLeft(
  Iterable<Map<String, dynamic>?> maps,
) {
  return (<Map<String, dynamic>?>[{}]..addAll(maps)).reduce(_recursivelyAddAll);
}

Additionally, both value from store.get(rootId) and patchData from patch.data[rootId] are Map<String, dynamic>?.

Thus, it would be safe and appropriate to update the type check as follows:

if (value is Map<String, dynamic> && patchData is Map<String, dynamic>) {
  // Perform the merge
}

Expected behavior

The readNormalized function should correctly merge the optimistic patch into the existing cached data.

Actual behavior

Currently, the readNormalized function replaces the stored existing data instead of merging it.

System configuration

Dart version:
Dart SDK version: 3.5.2 (stable) (Wed Aug 28 10:01:20 2024 +0000) on "macos_arm64"

Flutter version:

Flutter 3.24.2 • channel stable •
https://github.com/flutter/flutter.git
Framework • revision 4cf269e36d (3 months ago) •
2024-09-03 14:30:00 -0700
Engine • revision a6bd3f1de1
Tools • Dart 3.5.2 • DevTools 2.37.2

Package version:
graphql_flutter 5.2.0-beta.8

graphql 5.2.0-beta.9

@nozomemein nozomemein marked this pull request as ready for review December 17, 2024 23:13
@nozomemein nozomemein force-pushed the fix-read-normalized-patch branch from 98bd81d to 797ba78 Compare December 17, 2024 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant