Apollo GraphQL Remote and Local Queries - graphql

I'm working with apollo local state and hoping for help to better understand it and fix my query
Here is what my local apollo state looks like.
cache.writeData({
data: {
cart: {
items: [],
total: 0,
__typename: 'CART_INFORMATION'
},
}
});
Where item is an array of objects. Each object is in the form of
{
productId: string,
quantity: number
}
When my cart page loads, i want to fetch more details about my cart to display on the page. Here is my query.
export const GET_CART_PRODUCTS_DETAILS = gql`
query productDetails($productIds: [ID]!) {
cartProducts(productIds: $productIds) {
id
name
price
imageUrl
company
quantity #client
}
}
`;
My main concern is here quantity #client. Since the cartProducts query receives an array of ids, i'm not sure how its going to get the quantity of each product from my local state.
Any help/explanation about how to better structure this is appreciated. I have read the docs, but this use case wasn't covered so i don't know if its supported.

Related

Apollo read multiple values from cache

I'm looking for a way to read the value from cache for Apollo. On the page of Apollo, it only show the way to retrieve one as follow:
With readQuery
const READ_TODO = gql`
query ReadTodo($id: ID!) {
todo(id: $id) {
id
text
completed
}
}
`;
// Fetch the cached to-do item with ID 5
const { todo } = client.readQuery({
query: READ_TODO,
variables: { // Provide any required variables here. Variables of mismatched types will return `null`.
id: 5,
},
});
or with readFragment
const todo = client.readFragment({
id: 'Todo:5', // The value of the to-do item's cache ID
fragment: gql`
fragment MyTodo on Todo {
id
text
completed
}
`,
});
However, I can't select more than 1 todo at a time in both of them. I will have an array of IDs I'm not sure how many values will be in it. What is an excellent approach to this problem?

Why do I not have an Apollo cache-hit between a multi-response query and a single-item query for the same type?

I'm working on a vue3 project using #vue/apollo-composable and #graphql-codegen.
My index page does a search query. Each result from that query has a tile made on the page. I'm expecting the tile queries will be answered by the cache, but instead, they always miss.
At the page level I do this query:
query getTokens($limit: Int!) {
tokens(limit: $limit) {
...tokenInfo
}
}
Inside of the tile component I execute:
query getToken($id: uuid!){
token(id: $id) {
...tokenInfo
}
}
The fragment looks like this:
fragment tokenInfo on token {
id
name
}
Expectation: The cache would handle 100% of the queries inside the tile components. (I'm hoping to avoid the downfalls of serializing this data to vuex).
Reality: I get n+1 backend calls. I've tried a bunch of permutations including getting rid of the fragment. If I send the getToken call with fetchPolicy: 'cache-only' no data is returned.
The apollo client configuration is very basic:
const cache = new InMemoryCache();
const defaultClient = new ApolloClient({
uri: 'http://localhost:8080/v1/graphql',
cache: cache,
connectToDevTools: true,
});
const app = createApp(App)
.use(Store, StateKey)
.use(router)
.provide(DefaultApolloClient, defaultClient);
I'm also attaching a screenshot of my apollo dev tools. It appears that the cache is in fact getting populated with normalized data:
Any help would be greatly appreciated! :)
I've gotten this worked out thanks to #xadm's comment as well as some feedback I received on the Vue discord. Really my confusion is down to me being new to so many of these tools. Deciding to live on the edge and be a vue3 early adopter (which I love in many ways) made it even easier for me to be confused with the variance in documentation qualities right now.
That said, here is what I've got as a solution.
Problem: The actual problem is that, as configured, Apollo has no way to know that getTokens and getToken return the same type (token).
Solution: The minimum configuration I've found that resolves this is as follows:
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
token(_, { args, toReference }) {
return toReference({
__typename: 'token',
id: args?.id,
});
},
},
},
},
});
However, the feels.... kinda gross to me. Ideally, I'd love to see a way to just point apollo at a copy of my schema, or a schema introspection, and have it figure this out for me. If someone is aware of a better way to do that please let me know.
Better(?) Solution: In the short term here what I feel is a slightly more scalable solution:
type CacheRedirects = Record<string, FieldReadFunction>;
function generateCacheRedirects(types: string[]): CacheRedirects {
const redirects: CacheRedirects = {};
for (const type of types) {
redirects[type] = (_, { args, toReference }) => {
return toReference({
__typename: type,
id: args?.id,
});
};
}
return redirects;
}
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
...generateCacheRedirects(['token']),
},
},
},
});
If anyone has any improvements on these, please add a comment/solution! :)

Gatsby - making a dynamic query to Contentful

I'm working on a simple e-commerce based on Gatsby and Contentful.
One of my pages is CartPage.
So I'm trying to make a query to Contenful to get the necessary info about my products, so I want to pass a list of product ids that are in the user's cart.
Here's my component and query:
export interface CartPageProps {
data: {
products: GQL.ContentfulProductConnection;
};
}
const CartPage = ({ data }: CartPageProps) => {
const mockCart = [1, 2];
console.log(data);
return (
<IndexLayout>
<Page>
//
// Layout goes here
//
</Page>
</IndexLayout>
)
}
export default CartPage;
const pageQuery = graphql`
query ProductsInCart($mockCart: [Int], $lang: String) {
products: allContentfulProduct(filter: { idProduct: { in: [1, 2] }, node_locale: { eq: "en" } }) {
edges {
node {
id
idProduct
price
title
quantity
photo {
resize(height: 200, width: 200) {
src
}
}
slug
}
}
}
}
`;
Now all I get on a console.log(data) is undefined.
If I do the same query but with useStaticQuery with all the data hardcoded - I get the desired data. But useStaticQuery doesn't accept variables.
Am I doing something wrong here? How can I pass variables to the query and put my data to the props?
Gatsby uses GraphQL at build-time and not for live sites. as said here So it's not intended for making GraphQL queries on the go.

Why are my Apollo cache updates not reflected in my queries?

I am trying to use Apollo cache for local state management to store the state of a form so it can be returned to without clearing.
I am experiencing a problem where the cache is being updated but subsequent queries to the cache are returning stale data. I have experienced this problem in React components using the useQuery hook, and also in Apollo DevTools which I will use to demonstrate it below:
I have this mutation and query set in my resolvers (I am using Typescript):
const resolvers = {
Mutation: {
storeLetterDraft: (_root, args: { type: string, details: LetterSending }, { client, getCacheKey }) => {
const id = getCacheKey({
__typename: "LetterDraft",
id: args.type,
});
const data = { ...args.details };
client.writeFragment({
data,
id,
fragment: LETTER_SENDING_FRAGMENT,
});
},
},
Query: {
letterDraft: (_root, args: { type: string }, { client, getCacheKey }) => {
// I HAVE TRIED A DEBUGGER STATEMENT HERE
const id = getCacheKey({
__typename: "LetterDraft",
id: args.type,
});
return client.readFragment({
id,
fragment: LETTER_SENDING_FRAGMENT,
});
},
},
}
My fragment is:
export const LETTER_SENDING_FRAGMENT = gql`
fragment DraftLetterSending on LetterDraft {
date
firstName
lastName
addressLine1
addressLine2
addressTown
addressCounty
addressPostcode
}
`;
I am initialising my cache with:
cache.writeData({
data: {
letterDrafts: [{
__typename: "LetterDraft",
id: "CREATE",
addressCounty: "Northamptonshire",
addressLine1: "1 Watkin Terrace",
addressLine2: "",
addressPostcode: "NN1 3ER",
addressTown: "Northampton",
date: "2019-11-01",
firstName: "d",
lastName: "d",
}],
},
});
My mutation looks like:
export const storeCreateLetterSendingMutation = gql`
mutation StoreCreateLetterSending($details: LetterSending!) {
storeLetterDraft(type: "CREATE", details: $details) #client
}
`;
Before mutation, the cache in Apollo DevTools looks as expected:
And a query returns as expected:
After the mutation is performed, the cache updates:
However, running the query again results in the stale data:
Interestingly if I put a debugger statement in the part above (I HAVE TRIED A DEBUGGER STATEMENT HERE), then it seems the query resolver is run the first time, but not the second time, so it appears the query is being cached - even though it is the cache I am updating! Therefore I think the issue is with the query not running the resolver subsequently.
I had missed this from the documentation (there are various places on the Apollo website detailing the local cache and #client.
https://www.apollographql.com/docs/react/essentials/local-state/#forcing-resolvers-with-clientalways-true
While leveraging the cache for both local and remote results can be super helpful in a lot of cases, it's not always the best fit. We might want to use a local resolver to calculate a dynamic value that needs to be refreshed on every request, while at the same time continue to use the cache for the network based parts of our query. To support this use case, Apollo Client's #client directive accepts an always argument, that when set to true will ensure that the associated local resolver is run on every request.

With apollo client how do you set loading status on mutation refetchQueries?

So I'm adding to an object's list with a mutation using the apollo client. Say I have an event and I'm adding participants and i'm using refetchQueries to get the refreshed event with participants on the details screen. Unfortunately I'm not getting a data.loading == true status during this refetch, making it likely that the user will keep hitting that join participant button repeatedly.
<Button styleName="dark full-width" onPress={()=> {
this.props.joinEvent({
variables: { eventId: Event.id, userId: user.id },
refetchQueries: ['getEvent'],
});
// how do I trigger the data.loading = true like it does during the initial fetch?
}
}>
use awaitRefetchQueries:true, it will wait until refetch query resolves
awaitRefetchQuery
The Apollo React documentation isn't great on this, but there is an open issue to improve it.
Instead of looking at the data.loading property, you'll want to use data.networkStatus. It will change to 4 during a refetch (networkStatus values).
However, in order to get this value to update, you'll also need to set the notifyOnNetworkStatusChange option to true in your query options.
Using the React graphql higher-order component, this looks like:
var myQuery = gql`
query MyQuery {
...
}
`;
var myComponentWithData = graphql(myQuery, {
options: {
notifyOnNetworkStatusChange: true
}
}
)(MyComponent);

Resources