Skip to content
This repository has been archived by the owner on Jul 10, 2019. It is now read-only.

Using @client data as variables #231

Closed
tutturen opened this issue Mar 24, 2018 · 18 comments
Closed

Using @client data as variables #231

tutturen opened this issue Mar 24, 2018 · 18 comments

Comments

@tutturen
Copy link

I have this query, which fetches a list of item ids from local storage (cartItemIds = [1, 3, 5])

query LocalCartIds {
  cartItemsIds @client
}

This data is then passed to this query which asks the server to calculate the cart based on the ids.

query Cart($ids: [Int!]!) {
  cart(itemIds: $ids) {
    items {
      name
      price
    }
  }
  totalPrice
}

While this works, it does not sit right with me. Does the GraphQL way of "ask for what you want" apply here? I have to ask twice - first from the local cache, and then from the server.

Is there a better solution for achieving this?

@fbartho
Copy link
Contributor

fbartho commented Mar 25, 2018

You could make a apollo-link-state resolver that does this for you with the following steps:

  1. attach the apollo-client to the cache
client = new ApolloClient({ cache,})
cache.myClient = client;
  1. create the local resolver
    a. figure out your GraphQL API names, I'm just going to guess this:
query LocalCart() {
  localCart @client {
    items {
      name
      price
    }
  }
  totalPrice
}

2.b. write your resolver, extract {cache} from the context and extract client from cache.myClient.
2.c. In your resolver: do await client.readQuery( /* your second query */)

It's a good amount of boilerplate, but should work?

@tutturen
Copy link
Author

Thanks, @fbartho, that is certainly a way to do it!

What I am a bit worried about is attaching the client to the cache. From what I can understand, you have done it because there is no other way of getting access to the client in the resolvers. I would much rather be able to attach the client directly to the context.

@tutturen
Copy link
Author

Another question - how do you pass along the inner part of the query to the readQuery operation in the resolver? How do I tell my server that I want to fetch

items {
  name
  price
}

And that this can change dynamically by the incoming query to localCart?

@fbartho
Copy link
Contributor

fbartho commented Mar 26, 2018

@tutturen I'm not sure how easy it would be, you possibly could examine the other args to your resolver -- context or info (3rd & 4th arg to your resolver) probably has something like operation? and figure out the selection set from there.

As a fallback, you probably can get the string representation of the query off of the operation, and then with a regex that looks for localCart @client { you could dynamically build a query that way? (Smells like a hack).

Re: "there is no other way" Please bump this issue: apollographql/apollo-link#527 -- I want it to be easier than manually hooking it in.

@tutturen
Copy link
Author

@fbartho Thanks, those are good suggestions. Although I am getting the feeling that becomes a hack upon a hack. As a consumer of this library, I don't want to deal with that.

apollo query graph

In the server graph, you can freely define edged between your nodes. In the local (client) graph, you can also define resolvers as edges between your local nodes. In server graphQL queries in apollo-link-state, you can query as much client data as you want.

But you can't query any server data in a client query.

Unless you do as @fbartho suggests - attach the client to the cache and parse the inner query with regex. I would also like with a better API for it!

Ideally, I would like to have a binding attached to the incoming context object, which you could easily pass along the (obj, args, context) to, sort of like this:

const resolvers = {
  LocalCart: {
    cart: (obj, args, context) => {
      return context.query.cart({ products: obj.cartItemIds }, context);
    }
  }
}

@scragz
Copy link

scragz commented Apr 2, 2018

I just spent all day trying to figure out the syntax for setting a variable from the @client cached data so I can use the current ID which is saved locally and needed in a bunch of queries throughout the app.

I was totally convinced something like this would totally work and was missing from the documentation if I could just find the right syntax:

gql`query CurrentStoreQuery($id: ID! = app.currentStoreId @client) {
  store(id: $id) {
    id
    title
  }
}`

@tutturen
Copy link
Author

I think I just found the proposed solution for this - using the @export directive.

It is explained in this video by Lee Byron:
https://www.youtube.com/watch?v=ViXL0YQnioU&t=12m13s

This means that I (in theory) could write a query like this:

query Cart {
  cartItemIds @client @export(as: "ids")
  cart(itemIds: $ids) {
    items {
      name
      price
    }
  }
  totalPrice
}

@hwillson
Copy link
Member

Quick note - this is related to #168 (and is something we're planning on addressing for the 1.0 release).

@ancyrweb
Copy link

Stumbled on the same problem. Is this a thing yet ?

@wiadev
Copy link

wiadev commented Sep 11, 2018

@tutturen I have the same problem. I kept the category_ids in the local cache via link-state and I need to use it as an option variable via query. did you find a solution?

@wiadev
Copy link

wiadev commented Sep 11, 2018

Here is the example.

import { ProductFragment } from ‘./Fragments’;

export default gql`
 query searchProducts($term: String!, categoryIds: [Int], $page: Int!) {
   searchProducts(term: $term, categoryIds: $categoryIds, page: $page) {
     page
     pageSize
     products {
       ...ProductItem
     }
   }
 }
 ${ProductFragment}
`;
export default graphql(QUERY_PRODUCTS_BY_SEARCH_TERM, {
 options: props => ({
   variables: { term: props.navigation.getParam(‘searchTerm’), categoryIds, page: 1 },
 }),
})(SearchProductsList);

Here, categoryIds should be value from local cache(link-state).

@tutturen
Copy link
Author

To use variables in the local cache, or the need for an extra query, is precisely what I was trying to avoid. The team have acknowledged the problem, and will hopefully implement the feature in the 1.0 release.

@wiadev
Copy link

wiadev commented Sep 11, 2018

I found a solution for this.

Here is the code.

const QUERY_FILTER_CATEGORY_IDS = gql`
 query filterCategories {
   filterCategories @client {
     ids
   }
 }
`;

export default compose(
 graphql(QUERY_FILTER_CATEGORY_IDS, {
   props: ({
     data: {
       filterCategories: { ids },
     },
   }) => ({ ids }),
 }),
 graphql(QUERY_PRODUCTS_BY_SEARCH_TERM, {
   options: props => ({
     variables: {
       term: props.navigation.getParam(‘searchTerm’),
       categoryIds: props.ids,
       page: 1,
     },
   }),
 })
)(SearchProductsList);

An important note is that compose() executes the last enhancer first and works its way backwards through the list of enhancers. To illustrate calling three functions like this: funcC(funcB(funcA(component))) is equivalent to calling compose() like this: compose(funcC, funcB, funcA)(component).
https://www.apollographql.com/docs/react/api/react-apollo.html#compose

@tutturen
Copy link
Author

If I am not wrong, that solution will execute two queries, which is defeats the core of GraphQL specified on graphql.com:

All of the data you need, in one request

I appreciate that you are trying to help me, I really do. I have a working solution, but this is more about the inconvenience of having to execute two queries.

@wiadev
Copy link

wiadev commented Sep 11, 2018

composing the multi queries is completely fine and they are chained together.
https://www.apollographql.com/docs/react/api/react-apollo.html#compose
You can check out more details from the above.
it works for me like a charm.

FYI, composing make more sense to me since we can compose the complex state via link-state.

@export can be a solution for simple state but in practice, we have more complex structure.

@ak-rcg
Copy link

ak-rcg commented Apr 9, 2019

Any further info on this? I would like to use something like the following (I have a currentCar in local state, and want to use its id in a sub-detail page query):

const CarPurchaseDetails = gql`
  query CarPurchaseDetails($id: ID!) {
    currentCar {
      id
    } @client @export(as "id")
    carPurchasing(id: $id) {
      id
      policy
      ...

@hwillson
Copy link
Member

hwillson commented Apr 9, 2019

@ak-rcg This functionality is now available in apollo-client >= 2.5, and doesn't require using apollo-link-state. See the Using @client fields as variables section of the docs for more info.

@tutturen
Copy link
Author

tutturen commented Apr 9, 2019

Thanks a bunch @hwillson !

@tutturen tutturen closed this as completed Apr 9, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

7 participants