Apollo-client: Selecting data from the store

Created on 13 Dec 2016  路  11Comments  路  Source: apollographql/apollo-client

This is a continuation of https://github.com/apollostack/react-apollo/issues/232

With the recent result reducers update to Apollo Client, it is now possible to write data to the store with very high flexibility. The issue linked above discusses an API to be able to read from the store with high flexibility.

The API could expose a function via the options argument of the graphql HOC. This function, let's call it select for now, would internally use graphql-anywhere to allow the developer to query the store and get denormalized data back.

Here's an example of how it could be used: https://github.com/apollostack/react-apollo/issues/232#issuecomment-249756424

good-first-issue

Most helpful comment

All 11 comments

It's worth mentioning that it would be necessary to come up with an API that will allow querying the store in a deterministic way, i.e., because the store doesn't have a schema and the data is normalized then probably the most sensible thing is to be able to query single objects using their ID.

For example:

// `obj` here is some object that could be the result of a query or a mutation.
// `getDataIdFromObject` would have to be exposed by `apollo-client`.
select(gql`
  objectFromId(id: ${getDataIdFromObject(obj)}) {
    # Selection Set
  }
`);

Also explored here: https://github.com/apollostack/react-apollo/issues/232#issuecomment-249755185

An even better API would be to simply be able to query the store using the GraphQL API's schema. This would be much more complicated to implement though. For example:

// `obj` here is some object that could be the result of a query or a mutation.
// In this case, `obj.id` is simply the identifier for the GraphQL API.
select(gql`
  post(id: ${obj.id}) {
    # Selection Set
  }
`);

I'm not entirely sure this is possible though, and first there would need to be a way to map the post query to the part of the store that keeps those objects. Here's a previous discussion on that: https://github.com/apollostack/apollo-client/issues/706#issue-179112870

@migueloller I like the second option you described (reading from the store using simple graphql), and I think you can already do that with what we have now if you're absolutely sure that all the data is in the store. All you'd need to do is define the right client side resolvers to do a redirection of the query to the cache. If you defined a node client-side field, then you could theoretically use that to query any object by id (and maybe typename), because the global id is all you need to map it to the right place in the store.

Given that, I guess what we're really looking for is a standard way to expose this in a synchronous way and make sure it never hits the server. That's where select would come in.

Please tell me if I'm not making any sense here.

@helfer,

You're making perfect sense! 馃槃

Another way is to make updateQueries or result reducers support promises (and by extension async functions) and that would remove the need to have a synchronous select function.

Which do you think would make more sense in terms of API design?

The thing with using the GraphQL schema itself is that we have to depend on certain things and make certain assumptions. A better API might instead be fragment based. So we would have:

client.getFragmentData(gql`fragment on User { ... }`, '5')

Where the second argument is the id created by dataIdFromObject. It would also be nice to have a complementary set API:

client.setFragmentData(gql`fragment on User { ... }`, '5', { ... })

It we wanted to give the user even more control over the store.

@calebmer,

Given that the select function would never hit the server there's no need to use the GraphQL schema itself.

Also, with the API you show above, why wouldn't you simply provide a selection set and the object id (shown below)?

client.select(gql`{ ... }`, '5')

Is it to take advantage of GraphQL validation using the schema by looking at the fragment's type? Wouldn't this mean that you're using the GraphQL schema then? If so then I'm not sure what you meant by:

The thing with using the GraphQL schema itself is that we have to depend on certain things and make certain assumptions.

馃槄

What kind of assumptions and dependencies are you talking about?

You鈥檙e right that in this case a selection set would actually be the correct argument to take here alongside the data id. I鈥檝e been thinking about fragments a lot in other contexts, but yes a selection set would be much better here. Except for in the case where the user may have already defined a fragment elsewhere (say for a component) and they would like to use that fragment as their selection set. The main point being that doing this would allow a user to enter the store at arbitrary points which don鈥檛 have to be the root query.

As for the second point an API like this post(id: ${obj.id}) would require some assumptions about how a user has structured and identified their data which I believe you already recognized.

I definitely agree that an API like this should exist, but I feel like exposing the interface as an arbitrary GraphQL schema managed by Apollo Client is a little too clever. It may only serve to confuse users who may end up mixing up fields in their queries, and it loses some of the imperative/dynamic benefits that make a scripting language like JavaScript useful.

@calebmer,

What are your thoughts on my comment above suggesting this API:

// `obj` here is some object that could be the result of a query or a mutation.
// `getDataIdFromObject` would have to be exposed by `apollo-client`.
select(gql`
 objectFromId(id: ${getDataIdFromObject(obj)}) {
   # Selection Set
 }
`);

Where the id argument is determined by Apollo's implementation and getDataIdFromObject is pretty self explanatory.

Do you think this is better or worse than just providing the selection set?

Perhaps the query could be renamed from objectFromId to getObjectFromId so that we can have the complementary setObjectFromId?

I like the concept, my only objection is with the GraphQL syntax for retrieving the data 馃槈

You could implement the same thing with:

client.getObjectWithId(gql`{ ... }`, getDataIdFromObject(obj))

鈥r even more succinctly:

client.getObjectWithId(gql`{ ... }`, obj)

@calebmer, awesome! I think we can agree then that this is the best candidate as of now:

client.select(gql`{ ... }`, obj);
// or if the object isn't available?
client.select(gql`{ ... }`, '5');

馃槃

EDIT: Perhaps the string passed to gql can also be any valid document and only the selection set will be considered. That way, if you pass a previously created fragment or any other query then it will work out of the box!

If anyone wants to send a PR to implement this, we would be happy to merge 馃憤

Was this page helpful?
0 / 5 - 0 ratings