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

Add documentation for inserting data into the cache #2862

Closed
hyazel opened this issue Mar 2, 2023 · 7 comments
Closed

Add documentation for inserting data into the cache #2862

hyazel opened this issue Mar 2, 2023 · 7 comments
Labels
docs Focuses on documentation changes planned-next Slated to be included in the next release

Comments

@hyazel
Copy link

hyazel commented Mar 2, 2023

Question

Question

With Apollo version 1.0.x, what is the best way to add a new object in the cache of the result of a query ?

Context :

I have a screen with favorite tracks and I want to update this list whenever a track has been removed or added in favorite from an another page.

A bit like this example from demystifying cache normalization.

in code it gives :

const [mutate, { data, error }] = useMutation<
    AddTodoTypes.AddTodo, 
    AddTodoTypes.AddTodoVariables
  >(
    ADD_TODO,
    {
      update (cache, { data }) {
        const newTodoFromResponse = data?.addTodo.todo;
        const existingTodos = cache.readQuery<GetAllTodos>({
          query: GET_ALL_TODOS,
        });

        if (existingTodos && newTodoFromResponse) {
          cache.writeQuery({
            query: GET_ALL_TODOS,
            data: {
              todos: [
                ...existingTodos?.todos,
                newTodoFromResponse,
              ],
            },
          });
        }
      }
    }
  )

Coming back to iOS, before the apollo version 0.x there was a method transaction.update(query: query that could do the job (example here) but now with version 1.0.x we have to make a dedicated query marked as @apollo_client_ios_localCacheMutation to write into the cache.

I have 3 queries (listed at the end) :

  • query GetFavoriteTracks($first: Int!, $after: String)
  • query GetFavoriteTracks($first: Int!, $after: String) @apollo_client_ios_localCacheMutation
  • mutation StoreUserFavoriteTracks($trackId: ID!)

How ?

I'd like to update query GetFavoriteTracks($first: Int!, $after: String) cache when mutation StoreUserFavoriteTracks($trackId: ID!) has been done.

It would go for something like this :

public func markTrackAsFavorite(trackId: String) async throws
    -> StoreUserFavoriteTracksMutation.Data.StoreUserFavoriteTrack.Track? {
  
        // Add a track to favorite
        let favoritedTrack = try await client.perform(for: StoreUserFavoriteTracksMutation(trackId: trackId),
                                 queue: DispatchQueue.global(qos: .default)).storeUserFavoriteTrack?.track

         client.store.withinReadWriteTransaction { transaction in

            // Get favorites from cache
            let favoriteTracks = try transaction.read(query: GetFavoriteTracksQuery(first: 10, after: .some(cursor))).me?.favorites.tracks

At this point if I want to write in the GetFavoriteTracksQuery apparently I have no choice than using the GetFavoriteTracksLocalCacheMutation query :

try transaction.update(GetFavoriteTracksQueryLocalCacheMutation(first: 10, after: .some(cursor))) { data in

I'm a bit stucked here to add or remove a track object from the data track list which has the following type :

[GetFavoriteTracksQueryLocalCacheMutation.Data.Me.Favorites.Tracks.Edge]

-> How can I easily take the favoritedTrack defined upper and add it to the list ?

Queries used

query GetFavoriteTracks($first: Int!, $after: String) {
    me {
        favorites {
            tracks(first: $first, after: $after) {
                pageInfo {
                    hasNextPage
                    endCursor
                }
                edges {
                    node {
                        ...TrackDetailLite
                    }
                }
            }
        }
    }
}

query GetFavoriteTracks($first: Int!, $after: String) @apollo_client_ios_localCacheMutation {
    me {
        favorites {
            tracks(first: $first, after: $after) {
                pageInfo {
                    hasNextPage
                    endCursor
                }
                edges {
                    node {
                        ...TrackDetailLite
                    }
                }
            }
        }
    }
}
mutation StoreUserFavoriteTracks($trackId: ID!) {
  StoreUserFavoriteTrack(trackId: $trackId) {
    track {
      id
      title
    }
  }
}
@hyazel hyazel added the question Issues that have a question which should be addressed label Mar 2, 2023
@hyazel hyazel changed the title Best way to write on the cache Best way to update a list on the cache Mar 2, 2023
@teodorpenkov
Copy link

teodorpenkov commented Mar 10, 2023

Great question! What about removal from a list? How would you approach this with the new mechanism?

@AnthonyMDev AnthonyMDev added the docs Focuses on documentation changes label Sep 15, 2023
@AnthonyMDev
Copy link
Contributor

We've realized that there is a gap in our documentation currently around this process. We'll be using this issue to track the work to add that documentation, which should then ultimately answer your questions.

Thanks for your patience with us here.

@AnthonyMDev AnthonyMDev changed the title Best way to update a list on the cache Add documentation for inserting data into the cache Sep 15, 2023
@AnthonyMDev AnthonyMDev removed the question Issues that have a question which should be addressed label Sep 15, 2023
@AnthonyMDev AnthonyMDev added planned-next Slated to be included in the next release and removed awaiting response labels Sep 15, 2023
@Vincz
Copy link

Vincz commented Nov 24, 2024

Hey guys! Any update regarding the documentation about these kind of cache manipulations?
Only the documentation is missing or is there missing features also in the client to accomplish what we are used to with others Apollo client like the React one for example?

@calvincestari
Copy link
Member

I'm not sure what has been added to the cache documentation since this issue was created but this is still the current documentation for writing to the cache.

Only the documentation is missing or is there missing features also in the client to accomplish what we are used to with others Apollo client like the React one for example?

Apollo Client probably does have additional cache features that Apollo iOS does not share but I'm not 100% familiar with what those features are. The current mechanism to update the Apollo iOS cache is still through a local cache mutation.

@Vincz
Copy link

Vincz commented Nov 25, 2024

Hi @calvincestari! Thank you for your response.
I have been able to make it work by modifying the cache but I'm not sure if it is the right way to do it.
Here is my GraphQL:

# schema.graphql
type List {
    id
    name
}

query {
    lists: [List!]!
}
# operations.graphql
query getLists {
    lists: lists {
        ...ListFragment
    }
}

fragment ListFragment on List {
    id
    name
}
#localMutations.graphql
query ListsLocalCacheMutation @apollo_client_ios_localCacheMutation {
    lists {
        ...ListFragment
    }
}

My SchemaConfiguration cache key has been defined as follow:

public static func cacheKeyInfo(for type: ApolloAPI.Object, object: ApolloAPI.ObjectData) -> CacheKeyInfo? {
        if let id = object["id"] as? String {
            return CacheKeyInfo(id: id)
        }

        if let id = object["id"] as? Int {
            return CacheKeyInfo(id: String(id))
        }

        return nil
    }

Removing a deleted items from a cached collection

To handle the deletion, it seems ok. I just update my collection by removing the List that has been deleted.

apollo.perform(mutation: DeleteListMutation(id: listId)) { [weak self] result in
            self?.handleGraphQLResult(result) { _ in
                self?.apollo.store.withinReadWriteTransaction { transaction in
                    let cacheMutation = ListsLocalCacheMutation()

                    try transaction.update(cacheMutation) { (data: inout ListsLocalCacheMutation.Data) in
                            data.lists.removeAll(where: { $0.id == listId })
                    }
                }
            }
        }

Adding a freshly created item to a cached collection

Now, it's the adding I'm not sure. Usually, with the web client, we work with object cache references. But in these case, I haven't been able to just add a reference to the newly created list.
I ended up with something like that:

apollo.perform(mutation: CreateListMutation(name: name)) { [weak self] result in
            self?.handleGraphQLResult(result) { data in
                if let newList = data?.list {
                    self?.apollo.store.withinReadWriteTransaction { transaction in
                        let cacheMutation = ListsLocalCacheMutation()

                        try transaction.update(cacheMutation) { (data: inout ListsLocalCacheMutation.Data) in
                            data.lists.append(ListsLocalCacheMutation.Data.List(_dataDict: newList.__data))
                        }
                    }
                }
            }
        }

As you can see, I had to recreate an object of the proper type ListsLocalCacheMutation.Data.List to be able to add it to the data.lists. I think it's working as I created it using the same fragment used to create the original one.
Is there any other way to do it properly or would it be the recommended way?

If we can agree on the proper way to do these two things, I think these examples would be a nice addition to the documentation.

@calvincestari
Copy link
Member

I believe that's the correct way to do it. There are no cache API functions that let you insert the object by cache reference unfortunately.

One piece of advice is to be careful with the underscored methods and properties, such as _dataDict and __data. The underscore denotes a private method or property and they could change at any time making your code fragile should those change. If you have selection set initializers enabled in your codegen configuration it would be better to create the object through that initializer.

@calvincestari calvincestari closed this as not planned Won't fix, can't repro, duplicate, stale Dec 4, 2024
Copy link
Contributor

github-actions bot commented Dec 4, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Focuses on documentation changes planned-next Slated to be included in the next release
Projects
None yet
Development

No branches or pull requests

6 participants