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

How to perform cache manipulation after mutations / subscriptions? #357

Closed
Nickersoft opened this issue Aug 24, 2018 · 7 comments
Closed
Labels
caching docs Focuses on documentation changes

Comments

@Nickersoft
Copy link
Contributor

As a longtime user of Apollo's React / JS-based client, I know that a pretty common practice when performing mutations or subscriptions to update Apollo's internal cache so any updates can propagate throughout the rest of the app. This practice is heavily described in the docs on optimistic UI responses and subscriptions. For example, in JS this might look like:

            update: (proxy, { data: { submitComment } }) => {
              // Read the data from our cache for this query.
              const data = proxy.readQuery({ query: CommentAppQuery });
              // Add our comment from the mutation to the end.
              data.comments.push(submitComment);
              // Write our data back to the cache.
              proxy.writeQuery({ query: CommentAppQuery, data });
            }

I know questions about the cache have been asked here before, but there do not seem to be any examples, recommendations, docs, etc. on how you might be able to achieve this kind of manipulation.

I use ReSwift in my app and have considered passing in a custom store object, and am wondering if this might be the only way. Without ReSwift, any modification I make to an external cache object won't affect the client cache because Apollo copies the init parameter to an internal variable.

This seems like a pretty instrumental concept in the Apollo ecosystem... am I missing something?

@ghost ghost added the docs Focuses on documentation changes label Aug 24, 2018
@ashiemke
Copy link
Contributor

You can use ApolloStore's WithinReadWriteTansaction stuff to modify the contents of the cache. There is not good documentation right now, but you can probably find what you need by reading tests. See ReadWriteStoreTests.swift.

@Nickersoft
Copy link
Contributor Author

Hey @ashiemke! Thanks for the response. I definitely noticed the methods within the ApolloStore class that allow you to read and write queries, but I believe the issue is the fact that the store member of the ApolloClient class isn't explicitly public, making it internal by default. This means that while all of the code in this repo can access that variable, as an end user of this library, I can access that variable when using Apollo as a dependency. Therefore, I can access my client's store directly. I think a really easy fix might just be making this variable public, or adding modifier methods to ApolloClient that can update the internal store. Would it make sense for me to make a PR for this?

@Nickersoft
Copy link
Contributor Author

Hey, just wanted to give an update here seeing my last comment is coming up on being a month old. I explored this topic quite extensively, even adding the Apollo iOS xcodeproj as a direct dependency of my app so I could debug it properly. First of all, I tested my theory by making the client store and other needed class members public so I could modify the store from my app correctly. However, upon copying the code verbatim from ReadWriteFromStoreTests, I noticed that the code tested there doesn't actually work as expected. It works for the tests because you are defining the cache schema within the test, statically, however, in a production app it seems to read the cache differently. This leads to a number of cache misses, causing the promise surrounding the cache write calls to fail.

I'll explain a little more. So this is the call I was trying to make, a simple call updating the cache with a new message in a conversation after it is sent:

let mutationInput = TextMessageInput(
    text: textMessage,
    conversationId: conversationID
)

let mutation = NewTextMessageMutation(input: mutationInput)

_ = apollo.perform(mutation: mutation) { (result, error) in
    let msg = result?.data?.createTextMessage
    let resultMap = msg?.resultMap
    let message = msg?.fragments.textMessage
    
    let query = ConversationQuery(id: conversationID)

    do {
        try apollo.store.withinReadWriteTransaction { transaction in
            try transaction.update(
                query: query
            ) { (data: inout ConversationQuery.Data) in
                data.conversation?.messages?.insert(
                    ConversationQuery.Data.Conversation.Message(  id: "999",
                                                                  content: "test",
                                                                  authorId: "4",
                                                                  created: "20180901",
                                                                  unread: false),
                    at: 0
                )
            }
        }
    } catch {
        print("error")
    }
}

Yet the cache never updated. So I started to investigate the cache write method, only to find the promise was throwing this error before it could hit this line, ending the promise:

Error at path "conversation": missingValue

So I dug deeper, and found that during the executor call chain, a function is called to retrieve fields (keys) inside an object (the cache). Following this through, I found that a JSON missingValue error was being thrown. So I went ahead and printed out the cache during a read operation that is called and found this:

// info.cacheKeyForField:
conversation(id:12)

// object
["conversation": [...]]

It's trying to read the cache key from what appears to be the raw response of the mutation coming back from the server, which is why there's a cache miss. I updated the private func cacheKey(for object: JSONObject) -> String? method to only return the name of the query (omitting the parameters) and suddenly my cache writes started working.

This was all I could uncover. I unfortunately don't have a ton of context on how the Apollo cache actually works, but this at least seems to be an underlying bug from what I can tell. Hopefully someone has a little more knowledge about what a fix might be?

@rad182
Copy link

rad182 commented Oct 5, 2018

I'm interested with this feature as well. Hope anyone from apollo can help?

@DarkDust
Copy link

Given that there's no ability to selectively remove (#362) or update cache entries I have to fall back to either completely clear the cache or execute certain queries with cachePolicy: .fetchIgnoringCacheData. Both solutions result in unnecessary network access. So a way to manipulate the cache after a mutation has been run would be very welcome.

@designatednerd
Copy link
Contributor

@Nickersoft Since #413 Got merged, can this issue now be closed?

@designatednerd
Copy link
Contributor

Since I haven't heard back in a couple weeks and #413 has been merged, I'm going to close this one out. Please feel free to reopen if it's still causing issues in newer versions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
caching docs Focuses on documentation changes
Projects
None yet
Development

No branches or pull requests

5 participants