Apollo GraphQl react. How to clear query cache for all variable combinations? - caching

I am using apollo graphql in my react application.
Say I have the following query:
query ListQuery($filter: String!) {
items(filter: $filter) {
id
name
}
}
This query lets me query a list of items using a filter. Say I used filter string A, and then used filter string B. The cache would now contain two entries: ListQuery(A) and ListQuery(B).
Now let's say I use a mutation to add a new item. How can I remove all the cached queries from the cache? So in this case, I want to remove both ListQuery(A) and ListQuery(B) from the cache.
How can I accomplish this?

In your case, you can use the apollo's method
client.resetStore();
It will clear the previous cache and then load the active queries.

Try this:
query ListQuery($filter: String!) {
items(filter: $filter) {
id
name
}
},
fetchPolicy: 'no-cache'
More here:
https://www.apollographql.com/docs/react/caching/cache-interaction/#bypassing-the-cache

Try evicting the particular query object from the cache...
cache.evict({ id: "ROOT_QUERY", fieldName: "listQuery" });
cache.gc();
I got this working after reading this: https://danreynolds.ca/tech/2020/05/04/Apollo-3-Client-Cache/

Seems like what you need is refetchQueries option/prop to be set to array of query names strings. The documentation states that:
If options.refetchQueries is an array of strings then Apollo Client
will look for any queries with the same names as the provided strings
and will refetch those queries with their current variables.
So as an example for using graphql HOC you could try to do:
function SomeComponent(props) {
const performMutation = () => {
props.doStuff({ refetchQueries: ["ListQuery"] })
.then(/* ... */)
.catch(/* ... */)
}
return (/* ... */)
}
export default graphql(DO_STUFF, { name: "doStuff" })(SomeComponent)
Or with the Mutation component:
function SomeComponent(props) {
return (
<Mutation
mutation={DO_STUFF}
refetchQueries={mutationResult => ["ListQuery"]}>
{(doStuff, { loading, error }) => {
/* ... */
}}
</Mutation>
);
}
If this is somehow doesn't do what you need, there is also a workaround using update option.

I was able to solve this problem by first changing a value in my schema from required to not required, which resulted in values being queried again! After that, I reverted the change and have had no problems!

Read about fetch policies
options.fetchPolicy
--Edit:--
But it is only as a trick if you want reset only one query. To reset all store see answer by #Nitin : Set fetchPolicy to network-only. Refresh query. Return it to option of your choice.

Related

GraphQL Tag dynamic table name in query (apollo)

In my app every customer has a own table for its data. Depending on which customer was selected I want to dynamically build the tablename for the Graphql query.
For example:
// Query for customer 1
gql`
query overviewQuery {
customer_1 {
aggregate {
count
}
}
}
`
// Query for customer 2
gql`
query overviewQuery {
customer_2 {
aggregate {
count
}
}
}
`
I have the customer id in my vuex store and want to insert a variable into the query like in the following pseudocode which is not working.
const tblUserAggregateName = `customer_${this.$store.state.customer.customerId`
gql`
query overviewQuery {
${this.tblUserAggregateName} {
aggregate {
count
}
}
`
Is there an option how to do this or how can this problem be solved?
It is not an option to hardcode all different customer queries and selected them on runtime.
Thanks!
In the answers it was mentioned, that it is against best practice to dynamically change the table name of GraphQL queries.
As changing the complete database structure for all clients (they each have a separate Database with a own PostgreSQL schema name) is not an option, I found a solution the looks a little bit hacky and definitely not best practice but I thought I might be interesting to share for others with the same problem.
The approach is pretty easy: I write the query as a simple string where I could use variables and convert them later to the gql AST Object with graphql-tag
const query = () => {
const queryString = `
{
${customerId}_table_name {
aggregate {
count
}
}
}`
return gql`${queryString}`
}
Works, but I you have a better solution, I am happy to learn!
You can supply variables, but they should not be used to dynamically infer schema object names. This violates core concepts of GraphQL. Try this instead:
gql`
query overviewQuery ($customerId: ID!) {
customerData (id: $customerId) {
tableName
aggregate {
count
}
}
}`
Apollo provides great documentation on how to supply variables into a query here: https://www.apollographql.com/docs/react/data/queries/
The backend can then use the customerId to determine what table to query. The client making requests does not need to know (nor should it) where that data is or how it's stored. If the client must know the table name, you can add a field (as shown in the example)

Should a query in Apollo Client look for the results cached by different queries before making a network request?

I'm trying to figure out how queries in Apollo Client are supposed to interact with the cache.
Specifically, I want to know if we run a query that fetches all todos:
todos {
title
completed
}
And then later we run a query that fetches a single todo that was already fetched by the todos query and requests the exact same fields:
todo(id: $id) {
title
completed
}
Should the second query a) fetch the data from the cache, or b) make a network request?
My assumption was that it would be case A. This is based on this quote from an official Apollo blog post:
https://www.apollographql.com/blog/demystifying-cache-normalization/
For example, if we were to:
Perform a GetAllTodos query, normalizing and caching all todos from a backend
Call GetTodoById on a todo that we had already retrieved with GetAllTodos
...then Apollo Client could just reach into the cache and get the object directly without making another request.
However, in my app I kept getting case B, it was always making an additional network request even though I had already requested all the data in a different query.
I assumed that I was doing something wrong, so I checked out this Apollo Full-stack Tutorial repo (https://github.com/apollographql/fullstack-tutorial) and updated the LaunchDetails query to only request the same data that was already requested in the GetLaunchList query. This replicated the same scenario I detailed above with the todos.
The queries now look like this:
export const GET_LAUNCHES = gql`
query GetLaunchList($after: String) {
launches(after: $after) {
cursor
hasMore
launches {
...LaunchTile
}
}
}
${LAUNCH_TILE_DATA}
`;
export const GET_LAUNCH_DETAILS = gql`
query LaunchDetails($launchId: ID!) {
launch(id: $launchId) {
...LaunchTile
}
}
${LAUNCH_TILE_DATA}
`;
I ran the application, and found that a new network request was made for the LaunchDetails query, even though all the required data was already in the cache after the GetLaunchList query was run.
I haven't been able to find any answer to this in the documentation, and the results I'm seeing from the example tutorial app seem to be at odds with the quote from the blog piece above.
Is it the case that a query will only look to the cache if the query has already been run before? Can it not fetch cached data if that data was cached by a different query? Am I missing something?
Please see this better (in my opinion) answer here:
https://stackoverflow.com/a/66053242/6423036
Copying directly from that answer, credit to the author:
This functionality exists, but it's hard to find if you don't know what you're looking for. In Apollo Client v2 you're looking for cache redirect functionality, in Apollo Client v3 this is replaced by type policies / field read policies (v3 docs).
Apollo doesn't 'know' your GraphQL schema and that makes it easy to set up and work with in day-to-day usage. However, this implies that given some query (e.g. getBooks) it doesn't know what the result type is going to be upfront. It does know it afterwards, as long as the __typename's are enabled. This is the default behaviour and is needed for normalized caching.
Let's assume you have a getBooks query that fetches a list of Books. If you inspect the cache after this request is finished using Apollo devtools, you should find the books in the cache using the Book:123 key in which Book is the typename and 123 is the id. If it exists (and is queried!) the id field is used as identifier for the cache. If your id field has another name, you can use the typePolicies of the cache to inform Apollo InMemoryCache about this field.
If you've set this up and you run a getBook query afterwards, using some id as input, you will not get any cached data. The reason is as described before: Apollo doesn't know upfront which type this query is going to return.
So in Apollo v2 you would use a cacheRedirect to 'redirect' Apollo to the right cache:
cacheRedirects: {
Query: {
getBook(_, args, { getCacheKey }) {
return getCacheKey({
__typename: 'Book',
id: args.id,
});
}
},
},
(args.id should be replaced by another identifier if you have specified another key in the typePolicy)
When using Apollo v3, you need a typepolicy / field read policy:
typePolicies: {
Query: {
fields: {
getBook(_, { args, toReference }) {
return toReference({
__typename: 'Book',
id: args.id,
});
}
}
}
}
the query will make a network query.
todo(id: $id) {
title
completed
}
Apollo cache isn't very smart. It is just storage. You need to read/write for more complicated operations manually.
The reason for this is Apollo doesn't know about your schema and data structure. It doesn't know that todo(id: $id) will do DB search by, so it can't optimize to look in the cache.
If you don't want a second fetch, you have to implement your data fetch structure with fragment:
try {
return client.readFragment({
id: 'Todo:5', // The value of the to-do item's unique identifier
fragment: gql`
fragment TodoFragment on Todo {
id
title
completed
}
`,
});
} catch(_e) { // if no fragment is found there will be an error
client.query(QUERY, variables: { id: 5})
}
The way Apollo cache is that if you do two queries:
load todos
todos {
id
title
completed
}
load single todo
todo(id: $id) {
id
title
completed
}
If you list a list of todos and load the second one - it will update the todo data.

Unable to combine local and remote data in a single GraphQL query (Next.js + Apollo)

The setup:
My basic setup is a Next.js app querying data from a GraphQL API.
I am fetching an array of objects from the API and am able to display that array on the client.
I want to be able to filter the data based on Enum values that are defined in the API schema. I am able to pass these values programmatically and the data is correctly updated.
I want those filters to be persistent when a user leaves the page & come back. I was originally planning to use Redux, but then I read about apollo-link-state and the ability to store local (client) state into the Apollo store, so I set out to use that instead. So far, so good.
The problem:
When I try to combine the local query and the remote query into a single one, I get the following error: networkError: TypeError: Cannot read property 'some' of undefined
My query looks like this:
const GET_COMBINED = gql`
{
items {
id
details
}
filters #client
}
`
And I use it inside a component like this:
export default const Items = () => (
<Query query={GET_COMBINED}>
{({ loading, error, data: { items, filters } }) => {
...do stuff...
}}
</Query>
)
IF however, I run the queries separately, like the following:
const GET_ITEMS = gql`
{
items {
id
details
}
}
`
const GET_FILTERS = gql`
{
filters #client
}
`
And nest the queries inside the component:
export default const Items = () => (
<Query query={GET_ITEMS}>
{({ loading, error, data: { items } }) => {
return (
<Query query={GET_FILTERS}>
{({ data: { filters } }) => {
...do stuff...
}}
</Query>
)
}}
</Query>
)
Then it works as intended!
But it seems far from optimal to nest queries like this when a single query would - in theory, at least - do the job. And I truly don't understand why the combined query won't work.
I've stripped my app to its bare bones trying to understand, but the gist of it is, whenever I try to combine fetching local & remote data into a single query, it fails miserably, while in isolation both work just fine.
Is the problem coming from SSR/Next? Am I doing it wrong? Thanks in advance for your help!
Edit 2 - additional details
The error is triggered by react-apollo's getDataFromTree, however even when I choose to skip the query during SSR (by passing the ssr: false prop to the Query component), the combined query still fails. Besides, both the remote AND local queries work server-side when run separately. I am puzzled.
I've put together a small repo based on NextJS's with-apollo example that reproduces the problem here: https://github.com/jaxxeh/next-with-apollo-local
Once the app is running, clicking on the Posts (combined) link straight away will trigger an error, while Posts (split) link will display the data as intended.
Once the data has been loaded, the Posts (combined) will show data, but the attempt to load extra data will trigger an error. Reloading (i.e. server-rendering) the page will also trigger an error. Checkboxes will be functional and their state preserved across the app.
The Posts (split) page will fully function as intended. You can load extra post data, reload the page and set checkboxes.
So there is clearly an issue with the combined query, be it on the server-side (error on reload) or the client-side (unable to display additional posts). Direct writes to the local state (which bypass the query altogether) do work, however.
I've removed the Apollo init code for brevity & clarity, it is available on the repo linked above. Thank you.
Add an empty object as your resolver map to the config you pass to withClientState:
const stateLink = withClientState({
cache,
defaults: {
filters: ['A', 'B', 'C', 'D']
},
resolvers: {},
typedefs: `
type Query {
filters: [String!]!
}
`,
})
There's a related issue here. Would be great if the constructor threw some kind of error if the option was missing or if the docs were clearer about it.

Passing Apollo Client cache data with apollo Query

I have two queries:
const GET_FILTERS = gql`
query getFilters {
filters #client {
id
title
selected
}
}
`
And
const GET_POSTS = gql`
query getPosts {
posts {
id
author
status
}
}
`
First one fetches the data from a local state using apollo-link-state and the second one is a external call.
I have a HOC for fetching posts setup like this:
const withPosts = (Component) => (props) => (
<Query
query={GET_POSTS}
>
{({loading, data, error})} => {
if(loading) return null
if(error) return null
return <Component {...data} {...props}/>
}}
</Query>
)
The fetching of the posts is fine but what I would like to do is to add whatever is returned from GET_FILTERS query with every call to GET_POSTS query?
One thing I could do is to wrap withPost in to another, lets say withFilters HOC, and pass the result in to GET_POSTS query as variables but I have been wondering if there is any other way to access that data via some sort of context and for example cache.readQuery using only withPost HOC.
Apollo has 'native' tools for HOC pattern, you can compose many (named) queries and mutations to enhance component. IMHO it's much more readable and powerfull than using query/mutaions components.
You can pass queried (filter) values as variables via data.fetchMore - of course you can use query component for 'inner query'.
Using cache directly isn't required while query 'cache-first' fetchPolicy option can be used.

apollo-link-state defaults derived from query data?

Note: This is a followup question to my previous question about Apollo GraphQl Storing derived data
I'm using apollo-link-state to store data that's derived from query data. In this example the query data from the db includes some (x,y) points on a graph and the derived data is slope, moving average, acceleration etc.
My React graphing components need different combinations of original and derived data. Some need only the original.
I need derived data to be calculated only once and only when I query for it.
The example on the Apollo site seems to imply needing to trigger a mutation first but that seems wrong to me since each component that uses this derived data needs to trigger a mutation first to make sure it's initialized. I don't want to do a query and a mutation everywhere I need data.
So my question is: Can/should I use query resolvers in apollo-link-state or is there some better way of thinking about this?
UPDATE: I think their async example might be what I need but I need to work it through.
Figured it out. Don't know why this wasn't obvious to me to begin with but.... hindsight.
In the end, you just need to define your resolver to return something. The resolver can even make its own queries.
export const getProjectDerived = (_obj, { ProjectId }, { cache }, info) => {
const projQueryRes = cache.readQuery({
query: projQuery,
variables: {
ProjectId
}
})
const newObj = { ...something here... }
return newObj
}
Then just include it in the 'Query' section of the resolvers.
import { getProjectDerived } from './project'
const resolvers = {
Query: {
ProjectDerived: getProjectDerived
}
}
export default resolvers

Resources