Passing Apollo Client cache data with apollo Query - graphql

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.

Related

Graphql fetching one object from array

In GraphQL, is it possible to fetch data with a 'filtered' array (e.g. only with a single object) instead of an array with all the (multiple) objects? For instance, by providing the 'id' property for the selected object you want to get in the query.
If yes, what needs to be changed in the below query? Just wondering if this filtering can already be done server side.
const selectedId = 'n32j23h'
const query = gql`
query getData {
dataObj {
bigArray {
id
name
}
}
}
`;
Define a new query in your typeDefs:
query getDataById(id: ID!): returnObjectType
Implement a resolver for that query that uses the id parameter to filter your bigArray

composing queries in Apollo Client

i'm learning to use apollo client and I saw that it used to contain "compose" method which combines multiple queries, so we can call two queries in a single request without explicitly write a whole new query.
lets say I have 2 queries
const getUsersQuery = gql`query getUsers ($name:String!){
users{
name
}
}`
const getPokemonsQuery = gql`query getPokemon($id:ID!){
pokemon (id:$id) {
name
}
}`
So a compose method will return an object which I can use to fetch these two queries in single request and include the variables for the two queries.
I didn't find a way to do it in the latest version.

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)

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.

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

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.

Resources