Apollo-client: Map query result to another query's cache

Created on 26 Sep 2016  路  13Comments  路  Source: apollographql/apollo-client

I'm using Apollo to build a production React Native app and I'm super grateful for all the great work you guys have done. Kudos!

There are a couple of things that I think could improve and I know many of them are already in the works (e.g., reimplementing loading logic in react-apollo and restructuring the design around fragments in apollo-client). It's super annoying that loading can be undefined sometimes and it's impossible to work with fragments so I'm looking forward to those changes!

There is another improvement that I wanted to mention because I'm not sure if it is in the works and I would love apollo-client to have something like it.

Imagine you have the following 2 queries:

query ($userId: ID!) {
  user(id: $userId) {
    name
    email
    posts {
      edges {
        node {
          title
          createdAt
        }
      }
    }
  }
}
query ($postId: ID!) {
  post(id: $postId) {
    title
    createdAt
  }
}

If the second query returns a post that would've been included in the users's post from the first query, there should be no network request. Currently with apollo-client, because they're different queries, the cache doesn't know any better.

I propose that we add an option to ApolloClient, similar to dataIdFromObject, called queryIdFromObject (this name is probably a bad one) that will let you map the result of a query to another query's key in the internal Redux store, with the purpose of improving the cache.

Here's how the option would be used for the example above:

const client = new ApolloClient({
  queryIdFromObject: ({ __typename, id }) => `$ROOT_QUERY.${__typename}:${id}`,
});

Potentially, this option could only be allowed for the root query (I don't see it being beneficial elsewhere) and dataIdFromObject could be used internally so that the code above could be simplified to:

const client = new ApolloClient({
  // maybe this name is better?
  rootQueryNameFromObject: ({ __typename }) => singularize(__typename.toLowerCase()),
});

Regardless, this would greatly reduce the number of trips to the API that the app needs to make.

Thoughts?

EDIT: If the server is Relay compatible, then it would be even simpler because the relevant root query would be node and/or nodes

Most helpful comment

Hoping to get this done and documented before the end of the week! Things have been very busy around here because of the GraphQL Summit.

All 13 comments

This is an interesting idea; maybe instead of mapping a query to a part of another query, instead we should have a mechanism to map to a data id -- so dataIdFromField or something like that.

So in this case, we'd say "the root field post with variable id=X should map to the object with id='post:X'". I'm not sure what the API for that should look like.

@tmeasday,

Yes, that would be a nice way to go about it. Here are 2 possible APIs that we could think about:

  1. Add updateQueries to the options in client.query

    • This is mentioned briefly in apollostack/react-apollo#233

    • The API would work the exact same as updateQueries from client.mutate, except that one would be able to update queries from other query results (and not just mutation results)

  2. Define a map in the client constructor that will map some result object (internally it will probably use its data id) to some query (most likely the root query) or some variation of this

    • This is what we've been discussing so far

    • The API could look similar to the 2 examples I have above.

Which of the 2 seems more natural? Should we expand on one over the other or should we expand on both and see which API makes the most sense?

This is now implemented! #809 Just some tests to do.

@stubailo,

Thanks so much for implementing this! Would you mind posting a quick snippet of how the new API could be used to solve the issue mentioned above?

Great! Having this issue as well, the PR would reduce my app's network traffic dramatically. 馃憤

Hoping to get this done and documented before the end of the week! Things have been very busy around here because of the GraphQL Summit.

It looks like this is now documented here. Thanks for all the hard work!

Implemented in #921

Looking again at the original use case presented in the originating issue description, it doesn't look like result reducers solve this problem because the post query has a changing variable (id) and result reducers don't have a way to map the post(id: $postId) back to the internal dataId that the store uses.

@migueloller I'm not sure I understand what result reducers have to do with cache redirects?

@helfer, originally I believed that result reducers would solve the issue I mentioned above in https://github.com/apollostack/apollo-client/issues/706#issue-179112870. It looks like it's more of a cache redirect issue (as you say). Should a separate issue be opened or this same on reopened (and maybe the title changed)?

@migueloller Have you looked at #921? I think that should do what you need.

@helfer, ahh yes... Just read the docs. I initially thought #921 was for result reducers. Sorry about that! 馃槄

Was this page helpful?
0 / 5 - 0 ratings