graphql-codegen + typescript-svelte-apollo - Refetch not working? - apollo-client

(Edited)
Refetching with the generated query type seems to work different from the refetch function in Apollo Client useQuery. I don't understand how to phrase it - can anyone provide an example?
I'm realizing the problem is probably either my refetch is not properly phrased, or maybe the store is only hitting the cached query. I've been going over my code for days and I can't figure out what it could be. I've tried await blocks too.
The refetch worked with svelte-apollo, but i'm trying to eliminate that dependency. I've also tried Apollo Client's useQuery, but the whole point of graphql-codegen with typescript-svelte-apollo is to use the generated typescript wrapper for the query.
When I assign the generated query to a reactive constant in my Svelte front-end code,
$: observations = getObservations({ variables: { filter } });
the query does not refetch when i update the query variables, as I would expect.
This is how my svelte template is using the query. The filter object changes based on a form user input. I've tried this with an await block too.
<script lang="ts">
import { getObservations } from '$lib/generated';
$: observations = getObservations({ variables: { filter } });
function handleFilter(event) {
filter = event.detail;
}
</script>
{#if $observations.loading}
Loading...
{:else if $observations.error}
{$observations.error}
{:else if $observations.data}
{#each $observations.data['observations']['edges'] as edge}
<Item node={edge['node']} />
{/each}
{/if}
Since this plugin allows to use the query directly, without Apollo's useQuery, i'm not sure how to phrase a refetch.
If i do $observations.refetch(); inside handleFilter(e), i get an error
Property 'refetch' does not exist on type 'Readable<ApolloQueryResult<GetObservationsQuery> & { query: ObservableQuery<GetObservationsQuery, Exact<{ filter?: FilterObservationsInput; }>>; }>'.ts(2339)
There's nothing fancy in my config. Am I doing something wrong here?
schema: src/graphql/schema.graphql
documents:
- src/graphql/queries.graphql
- src/graphql/mutations.graphql
generates:
src/lib/generated.ts:
plugins:
- typescript
- typescript-operations
- graphql-codegen-svelte-apollo
config:
clientPath: src/lib/shared/client
# asyncQuery: true
scalars:
ISO8601Date: Date
ISO8601DateTime: Date
Here's the client:
export default new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
observations: relayStylePagination(),
},
},
},
})
});
The generated query:
export const getObservations = (
options: Omit<
WatchQueryOptions<GetObservationsQueryVariables>,
"query"
>
): Readable<
ApolloQueryResult<GetObservationsQuery> & {
query: ObservableQuery<
GetObservationsQuery,
GetObservationsQueryVariables
>;
}
> => {
const q = client.watchQuery({
query: GetObservationsDoc,
...options,
});
var result = readable<
ApolloQueryResult<GetObservationsQuery> & {
query: ObservableQuery<
GetObservationsQuery,
GetObservationsQueryVariables
>;
}
>(
{ data: {} as any, loading: true, error: undefined, networkStatus: 1, query: q },
(set) => {
q.subscribe((v: any) => {
set({ ...v, query: q });
});
}
);
return result;
}
Here's the query document that it's built from:
query getObservations($filter: FilterObservationsInput) {
observations(filter: $filter) {
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
cursor
node {
id
createdAt
updatedAt
when
where
imgSrcThumb
imgSrcSm
imgSrcMed
thumbImage {
width
height
}
name {
formatName
author
}
user {
name
login
}
rssLog {
detail
}
}
}
}
}

Related

Getting an error when having heuristic fragment matching with Apollo & Nuxt.js

I am trying to connect the below graphql query with nuxtjs.
query getContent($slug: String!) {
contents (filters: { slug: { eq: $slug } }) {
data {
id
attributes {
title
content {
__typename
... on ComponentContentParagraph {
content
}
}
}
}
}
}
I am getting the following error and not getting the result from the query.
You are using the simple (heuristic) fragment matcher, but your queries contain union or interface types. Apollo Client will not be able to accurately map fragments. To make this error go away, use the `IntrospectionFragmentMatcher` as described in the docs: https://www.apollographql.com/docs/react/advanced/fragments.html#fragment-matcher
I have checked the questions and answers available here.
Apollo+GraphQL - Heuristic Fragment Manual Matching
I have followed the docs from apollo as well.
https://www.apollographql.com/docs/react/data/fragments/#fragment-matcher
I managed to generate possibleTypes as mentioned in the docs.
Here is my next config.
apollo: {
includeNodeModules: true,
clientConfigs: {
default: "~/graphql/default.js",
},
},
This is the default.js
import { InMemoryCache } from "apollo-cache-inmemory";
import possibleTypes from "./possibleTypes.json";
export default () => {
return {
httpEndpoint: process.env.BACKEND_URL || "http://localhost:1337/graphql",
cache: new InMemoryCache({ possibleTypes }),
};
};
I am using strapi for the backend and this query works fine when running from graphql interface.

Apollo Client: How to query rest endpoint with query string?

I'm using Apollo to call a rest endpoint that takes variables from query string:
/api/GetUserContainers?showActive=true&showSold=true
I'm having trouble figuring out how to pass variables to the query, so it can then call the correct url. From looking at apollo-link-rest docs and a few issues I think I'm supposed to use pathBuilder but this is not documented and I haven't been able to get it working.
So far I've defined my query like this:
getUserContainersQuery: gql`
query RESTgetUserContainers($showActive: Boolean, $showSold: Boolean, $pathBuilder: any) {
containerHistory #rest(type: "ContainerHistoryResponse", pathBuilder: $pathBuilder) {
active #type(name: "UserContainer") {
...ContainerFragment
}
sold #type(name: "UserContainer") {
...ContainerFragment
}
}
}
${ContainerFragment}
`
and calling it in my component like this, which does not work:
import queryString from 'query-string'
// ...
const { data } = useQuery(getUserContainersQuery, {
variables: {
showActive: true,
showSold: false,
pathBuilder: () => `/api/GetUserContainers?${queryString.stringify(params)}`,
},
fetchPolicy: 'cache-and-network',
})
The only way I got this to work was by passing the fully constructed path to the query from the component:
// query definition
getUserContainersQuery: gql`
query RESTgetUserContainers($pathString: String) {
containerHistory #rest(type: "ContainerHistoryResponse", path: $pathString) { // <-- pass path here, instead of pathBuilder
// same response as above
}
}
`
// component
const params = {
showActive: true,
showSold: false,
}
const { data } = useQuery(getUserContainersQuery, {
variables: {
pathString: `/api/GetUserContainers?${queryString.stringify(params)}`,
},
fetchPolicy: 'cache-and-network',
})
These seems to me like a really hacky solution which I'd like to avoid.
What is the recommended way to handle this query string problem?
You shouldn't need to use the pathBuilder for simple query string params. You can pass your params directly as variables to useQuery then pass then directly into teh path using the {args.somearg} syntax. The issue I see is you've not defined the variables your using for you query containerHistory bu only in the query alias RESTgetUserQueries. If updated is should look like this:
// query definition
getUserContainersQuery: gql`
query RESTgetUserContainers($showActive: Boolean, $showSold: Boolean) {
// pass the variables to the query
containerHistory(showActive:$showActive, showSold:$showSold) #rest(type: "ContainerHistoryResponse", path:"/api/GetUserContainers?showActive={args.showActive}&showSold={args.showTrue}") {
//.. some expected reponse
}
}
`
// component
const params = {
showActive: true,
showSold: false,
}
const { data } = useQuery(getUserContainersQuery, {
variables: {
showActive,
showSold
},
fetchPolicy: 'cache-and-network',
})

Multiple graphql queries in Gatsby component

I need to run multiple graphQL queries within a component and within the gatsby-node.js file. (Because Prismic is limited to 20 entries per answer...🙄)
I tried the following, just to see if I could create the graphql loop in the default function:
export default () => {
async function allPosts() {
let data
await graphql(`
query allDitherImages {
prismic {
allProjects(sortBy: meta_firstPublicationDate_DESC) {
totalCount
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
node {
cover_image
cover_imageSharp {
name
}
}
}
}
}
}
`).then(initialRes => {
data = initialRes
})
return data
}
allPosts().then(result => {
console.log(result)
})
return null
}
But then Gatsby tells me that Gatsby related 'graphql' calls are supposed to only be evaluated at compile time, and then compiled away. Unfortunately, something went wrong and the query was left in the compiled code.
How can I run multiple graphql queries?
Thank you in advance :)
Michael
The gatsby-source-prismic-graphql package will create pages for all of your Prismic items (more than just the first 20), as it iterates over all items under the hood, so I'd advise looking into using that if you are looking to generate pages for all of those items.
But if you need to get all items and pass them in the pageContext or something, you'll need to do the recursion yourself in the gatsby-node.
In the gatsby-node, after you have defined the query, you can use something like this to iterate over the results and push to an array.
let documents = [];
async function getAllDocumentsRecursively (query, prop, endCursor = '') {
const results = await graphql(query, { after: endCursor })
const hasNextPage = results.data.prismic[prop].pageInfo.hasNextPage
endCursor = results.data.prismic[prop].pageInfo.endCursor
results.data.prismic[prop].edges.forEach(({node}) => {
documents.push(node)
});
if (hasNextPage) {
await getAllDocumentsRecursively(query, 'allDitherImages ', endCursor)
}
}
await getAllDocumentsRecursively(documentsQuery, 'allDitherImages ');
Then in your createPage, pass the array into the context:
createPage({
path: `/`+ node._meta.uid,
component: allDitherTempate,
context: {
documents: documents
}
})

Store error: the application attempted to write an object with no provided typename but the store already contains an object

After mutation when I am updating the cache, changes are reflected in UI but getting the below error
Invariant Violation: Store error: the application attempted to write an object with no provided typename but the store already contains an object with typename of ItemCodeConnection for the object of id $ROOT_QUERY.itemCodes({"filter":{"number":10000001}}). The selectionSet that was trying to be written is:
{"kind":"Field","name":{"kind":"Name","value":"itemCodes"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"itemCodes"},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"itemCodeTile"},"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}}
GraphQL query:
const CREATE_ITEM_CODE_SPEC = gql`
mutation createItemCodeSpec($input: createItemCodeSpecInput) {
createItemCodeSpecification(input: $input){
__typename
id
itemCode {
number
}
product
spec_class
grade
}
}
`
const GET_ITEM_CODE = gql`
query itemCode($filter: filterInput){
itemCodes(filter: $filter){
itemCodes {
number
type
description
group
item_code_spec {
id
itemCode {
number
}
product
spec_class
grade
}
created_on
created_by
changed_on
changed_by
}
}
}
`
Below is the mutation:
const [mutation, { data, loading, error}] = useMutation(
CREATE_ITEM_CODE_SPEC,
{
update(cache, { data: { createItemCodeSpecification } }){
const currentData = cache.readQuery({
query: GET_ITEM_CODE,
variables: { filter : {number:itemCode} }
})
cache.writeQuery({
query: GET_ITEM_CODE,
variables: { filter : {number:itemCode} },
data: {
...currentData,
itemCodes: {
itemCodes: currentData.itemCodes.itemCodes.map((itemCode, index) => {
return {
...itemCode,
item_code_spec: index === 0? [
...itemCode.item_code_spec,
createItemCodeSpecification
] : itemCode.item_code_spec
}
})
}
}
})
}
}
);
You simply need to add "id" for each subsection of your query. Adding "id" for "itemCodes" in your GET_ITEM_CODE query might solve your problem.
You have fields missing in your response mutation.
Basically, you should make your mutation results have all of the data necessary to update the queries previously fetched.
That’s also why is a best practice to use fragments to share fields among all queries and mutations that are related.
To make it work both query and mutation should have exactly the same fields.
Have a look here to see more in depth how cache updates work:
https://medium.com/free-code-camp/how-to-update-the-apollo-clients-cache-after-a-mutation-79a0df79b840

GraphQL disable filtering if filter variable is empty

I have a Gatsby GraphQL query for a list of posts ordered by date and filtered by category.
{
posts: allContentfulPost(
sort: {fields: [date], order: DESC},
filter: {category: {slug: {eq: $slug}}}
) {
edges {
node {
title {
title
}
date
}
}
}
}
Right now when $slug is the empty string "", I get
{
"data": {
"posts": null
}
}
Is there a way to get all posts instead?
You can use the regex filter to your advantage. If you pass an empty expression, then all posts will be returned because everything will match.
query Posts($slugRegex: String = "//"){
posts: allContentfulPost(
sort: {fields: [date], order: DESC},
filter: {category: {slug: {eq: $slugRegex}}}
) {
# Rest of the query.
}
}
By default, all posts will be returned (the $slugRegex is an empty regex if nothing was passed). When the $slugRegex becomes a meaningful expression, then only matching posts will show up.
As for passing the value, I'm assuming you're using gatsby-node.js to create pages. In that case, it's as simple as that:
// gatsby-node.js
exports.createPages = async ({ actions }) => {
const { createPage } = actions
// Create a page with only "some-slug" posts.
createPage({
// ...
context: {
slugRegex: "/some-slug/"
}
})
// Create a page with all posts.
createPage({
// ...
context: {
// Nothing here. Or at least no `slugRegex`.
}
})
}
It's not possible with this query, even #skip/#include directives won't help because you can't apply them on input fields.
I would suggest to either adjust the server side logic so that null in the 'eq' field will ignore this filter or either to edit the query being sent (less favorable imo).
It seems that the graphql schema that you work against lacks the filtering support you need..
If anyone requires a solution for other systems than Gatsby this can be accomplished using #skip and #include.
fragment EventSearchResult on EventsConnection {
edges {
cursor
node {
id
name
}
}
totalCount
}
query Events($organizationId: UUID!, $isSearch: Boolean!, $search: String!) {
events(condition: { organizationId: $organizationId }, first: 100)
#skip(if: $isSearch) {
...EventSearchResult
}
eventsSearch: events(
condition: { organizationId: $organizationId }
filter: { name: { likeInsensitive: $search } }
first: 100
) #include(if: $isSearch) {
...EventSearchResult
}
}
Then in your client code you would provide search and isSearch to the query and get your events like:
const events = data.eventsSearch || data.events

Resources