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

Cache mutations are somewhat painful to work with #3327

Closed
BrentMifsud opened this issue Jan 26, 2024 · 5 comments
Closed

Cache mutations are somewhat painful to work with #3327

BrentMifsud opened this issue Jan 26, 2024 · 5 comments
Labels
feature New addition or enhancement to existing solutions

Comments

@BrentMifsud
Copy link

BrentMifsud commented Jan 26, 2024

Use case

Today if I want my query to have a local cache mutation, I need to do the following:

// returns an array of announcement fragments
query Announcements($locationID: ID!) {
    announcements(locationId: $locationID) {
        ...AnnouncementFragment
    }
}

query AnnouncementsLocalCacheMutation($locationID: ID!) @apollo_client_ios_localCacheMutation {
    announcements(locationId: $locationID) {
        ...AnnouncementFragment
    }
}

The result of this is that codegen creates duplicate types:

AnnouncementsQuery.Data.Announcement
AnnouncementsLocalCacheMutation.Data.Announcement

If I ever wanted to use a local cache mutation to insert a new item, I can't simply do:

var myNewAnnouncement: AnnouncementFragment

store.withinReadWriteTransaction { transaction in
     try transaction.update(AnnouncementsLocalCacheMutation(locationID: locationID)) { selectionSet in
          selectionSet.items.append(myNewAnnouncement)
     }
}

instead I have to do the following:

var myNewAnnouncement: AnnouncementFragment

store.withinReadWriteTransaction { transaction in
     try transaction.update(AnnouncementsLocalCacheMutation(locationID: locationID)) { selectionSet in
          selectionSet.items.append(
              AnnouncementsLocalCacheMutation.Data.Announcement(
                 // line by line map each field from myNewAnnouncement to this initializer.
                 // this is compounded by any subtypes that are also duplicated
              )
          )
     }
}

Alternatively, I can result to using the _fieldData or __data initializers to do this in one line, but this is not type safe and can fail.

Describe the solution you'd like

Ideally, the code gen should be smart enough to use a single type for both the local cache mutation and the query, so that it is easy to perform cache mutations. Or at the very least, generate convenience initializers in the cache mutations that can utilize the query data types.

Perhaps any queries marked with @apollo_client_ios_localCacheMutation should have an equivalent query and cache mutation generated together with the same types.

@BrentMifsud BrentMifsud added the feature New addition or enhancement to existing solutions label Jan 26, 2024
@AnthonyMDev
Copy link
Contributor

Thanks for the feedback @BrentMifsud. We've had a lot of similar feedback from people on this feature, so you are not alone. We do intend to deprecate LocalCacheMutation and allow you to make any selection set mutable in a future update.

In the mean time, the easiest way to work with this is to put the @apollo_client_ios_localCacheMutation directive on the shared AnnouncementFragment itself. You can use this fragment in a query, but it will also be usable for local cache mutations.

Copy link
Contributor

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo iOS usage and allow us to serve you better.

@BrentMifsud
Copy link
Author

BrentMifsud commented Jan 26, 2024

Thanks for the feedback @BrentMifsud. We've had a lot of similar feedback from people on this feature, so you are not alone. We do intend to deprecate LocalCacheMutation and allow you to make any selection set mutable in a future update.

In the mean time, the easiest way to work with this is to put the @apollo_client_ios_localCacheMutation directive on the shared AnnouncementFragment itself. You can use this fragment in a query, but it will also be usable for local cache mutations.

@AnthonyMDev Thanks for the response.

Do you have any examples of this in practice? I have added the local cache mutation directive on my fragment, but there does not seem to be any option to insert a fragment.

// from graphqlclient
func updateCachedQuery<CacheMutation: LocalCacheMutation>(
        cacheMutation: CacheMutation,
        mutator: @escaping (inout CacheMutation.Data) -> Void
    ) {
        store.withinReadWriteTransaction { transaction in
            try transaction.update(cacheMutation) { selectionSet in
                mutator(&selectionSet)
            }
        }
    }

image

Here is my codegen config (in case im missing something):

{
  "schemaName" : "Redacted",
  "input" : {
    "operationSearchPaths" : [
      "Redacted/Modules/GraphQL/Fragments/*.graphql",
      "Redacted/Modules/GraphQL/Mutations/*.graphql",
      "Redacted/Modules/GraphQL/Queries/*.graphql"
    ],
    "schemaSearchPaths" : [
      "Redacted/Modules/GraphQL/schema.graphqls"
    ]
  },
  "output" : {
    "testMocks" : {
      "none" : {}
    },
    "schemaTypes": {
      "moduleType": {
          "embeddedInTarget": {
            "name": "Redacted"
          }
      },
      "path": "Redacted/Modules/GraphQL/Generated/"
    },
    "operations" : {
      "inSchemaModule": {}
    }
  },
  "options": {
      "selectionSetInitializers" : {
        "operations": true,
        "namedFragments": true,
        "localCacheMutations" : true
      },
      "deprecatedEnumCases": "include",
      "schemaDocumentation": "include",
      "warningsOnDeprecatedUsage": "include",
      "conversionStrategies": {
          "enumCases": "camelCase"
      },
      "pruneGeneratedFiles": true
   }
}

@AnthonyMDev
Copy link
Contributor

Ah, I see. This is still a bit difficult to do. Which is why we want to make the changes I mentioned above.

Check out the docs here. You'll want to use the updateObject(ofType:withKey:) function. Since you are actually trying to append the new announcement to a list of announcements, you'll want the announcements` field in your mutable fragment.

fragment AnnouncementsListFragment @apollo_client_ios_localCacheMutation on Query {
    announcements(locationId: $locationID) {
        ...AnnouncementFragment
    }
}

When mutating the cache, you'll need to know the cache ID of the entity that has the announcements field on it. But in your case, it's just on the root query, which has the cache key QUERY_ROOT. You'll also need to pass in the correct locationID variable.

Try this:

var myNewAnnouncement: AnnouncementFragment

store.withinReadWriteTransaction { transaction in 
  transaction.updateObject(
    ofType: AnnouncementsListFragment.self,
    withKey: "QUERY_ROOT",
    variables: ["locationID": "myLocationID"]
  ) { (announcements inout AnnouncementsListFragment)
    announcements.append(myNewAnnouncement)
  }
}

There is definitely a lot to be desired with the current APIs for this. Hopefully this will get you where you need to be for now, but we definitely want to improve on this.

@BrentMifsud
Copy link
Author

Ahhh I see. I never thought of making an array into a fragment. Will try that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New addition or enhancement to existing solutions
Projects
None yet
Development

No branches or pull requests

2 participants