Creating dynamic graphql query using apollo/client - graphql

I am trying to create a graphql query where the query data type and filter parameters will be passed dynamically based on user input.
I have written the below query which filters using only one field shipdate.
const GET_SHIPDATA_WITH_FILTER = gql`
query GetShipData($shipdateStart: timestamptz, $shipdateEnd: timestamptz, $limit: Int) {
shipdata(where: {shipdate: { _gte: $shipdateStart, _lte: $shipdateEnd}},limit: $limit) {
status
import_time
shipdate
}
}
`;
const variables = {
shipdateStart: "some date",
shipdateEnd: "some date",
limit: 50,
};
If no filter is passed I'm using this one
const GET_SHIPDATA = gql`
query GetShipData($limit: Int) {
shipdata(limit: $limit) {
status
import_time
shipdate
}
}
`;
const variables = {
limit: 50,
};
You can see I have written two queries to handle two types of filters which won't work if I want to add more filters.
Now I am trying to write a single dynamic query where if the user wants to add more filters like status: {_eq: $status} or import_time: { _gt: $importTimeStart, _lt: $importTimeEnd} then I will pass the variables and the query will dynamically handle the filters. Something like
const GET_SHIPDATA = gql`
query GetShipData($allfilters: AllFilterTypes) {
shipdata(filter: $allfilters) {
status
import_time
shipdate
}
}
`;
const variables = {
//pass allFilters based on user input,
};
Btw I'm using react and hasura if it helps anyway.

Hasura already exposes types in your GraphQL schema that refer to "filter conditions". In Hasura, they're called Bool_Exp (short for boolean expression) and they map directly to the where clause.
If you just update your query to receive a shipdata_bool_exp you'll be able to build up a dynamic filter expression in your application code and it will work as expected.
const GET_SHIPDATA_WITH_FILTER = gql`
query GetShipData($filter: shipdata_bool_exp!) {
shipdata(where: $filter,limit: $limit) {
status
import_time
shipdate
}
}
`;

Related

Use variables in Shopify Bulk Operations graphql mutation

I am trying to use graphql to query a store's orders in a bulk operation. I am using a tags variable in the graphql request to query orders with specific tags. I have been following Shopify's documentation in order to do this.
Here is what I've been trying:
const variables = {
"tags": "tag:foo AND tag:bar"
};
const query = gql`
mutation getOrders($tags: String!) {
bulkOperationRunQuery(
query:"""
query {
orders(query: $tags) {
edges {
node {
id
email
}
}
}
}
"""
) {
bulkOperation {
id
url
status
}
userErrors {
field
message
}
}
}
`;
const bulkOperation = await graphQLClient.request(query, variables);
However, I always get this error back from the api:
Variable $tags is declared by getOrders but not used
Does anyone know if there is a way to use a graphql variable in that bulk operation? Thanks!
""" is multiline string based on docs, which would means its just a string and no vars are allowed. You can use js variable substitution like ${tags} inside templated string and bypass this all together or try if concatenation works like """ 1st part """" + $tags + """ 2nd part """

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

Dynamically choose query variables in React Apollo

I'd like to be able to dynamically choose which query variables I use in GraphQL.
For example, it seems a little redundant to need three separate queries:
const getAllStops = gql`
query trafficStops {
trafficStops {
id
date
}
}
`
const getStopsAfter = gql`
query trafficStops($after: String!) {
trafficStops(after: $after) {
id
date
}
}
`
const getStopsBefore = gql`
query trafficStops($before: String!) {
trafficStops(before: $before) {
id
date
}
}
`
Is there a way in which I could pass not just the variables before or after but whether I'd like to use one, the other, neither, or both into a single query instead of having multiple queries?
Yes, you just have to make your arguments optional. The exclamation mark at String! requires the argument to be a string and not null. Hence, by removing it you could write your single query as
const getAllStops = gql`
query trafficStops($after: String, $before: String) {
trafficStops(after: $after, before: $before) {
id
date
}
}
`

How to generate the same graphql query with different fields

I'm using graphql-tag so i'm going to use that syntax.
Lets say I have this query:
const query = gql`
query user(id: String) {
user(id: $id) {
id
}
}
`
Whats the best patten to reuse that same query document node if on a different call I want the fields username and email in addition to id without having to rewrite the entire query again like:
const query = gql`
query user(id: String) {
user(id: $id) {
id
username
email
}
}
`
I'm using react-apollo on the frontend if that makes things anymore interesting.
Edit:
Just to clarify... something like this
const userIdFrag = gql`
fragment UserId on User {
id
}
`
const fullUserFrag = gql`
fragment FullUser on User {
id
username
email
}
`
const generateQuery = (documentNode) => {
return gql`
query user(id: String) {
user(id: $id) {
...documentNode
}
}
${documentNode}
`
}
const idQuery = generateQuery(userIdFrag);
const fullUserQuery = generateQuery(fullUserFrag);
(The above does work but give me errors from graphql in the console, which leads me to believe this is not something I should be doing)
Based on your comment the following should work:
const generateQuery = (documentNode, fragment) => {
return gql`
query user(id: String) {
user(id: $id) {
...${fragment}
}
}
${documentNode}
`
}
const idQuery = generateQuery(userIdFrag, 'UserId');
const fullUserQuery = generateQuery(fullUserFrag, 'FullUser');
Basically the fragment name used is the actual one that needs to be spread while the whole documentNode object is put at the end, after query's closing bracket
I am not the very expert on the topic, but here is what I have been able to find out. (if you see any mistakes in my assumptions, let me know).
I found this article that makes some good points against dynamically generating gql queries/mutations. It seems like you get some nice benefits with the static approach, although it's a bit more typing.
But, in case you do need to have dynamic fields, I haven't been able to find anything bad about using the #skip directive GraphQL provides. Here the docs ref.
For the case of using it in react-apollo they also have it in their docs.
So, your code can end up looking something like this:
const query = gql`
query user($id: String, $skipUserMeta: Boolean!) {
user(id: $id) {
id
username #skip(if: $skipUserMeta)
email #skip(if: $skipUserMeta)
}
}
`
You just pass the skipUserMeta as a variable alongside the id field.
NOTE: I actually found a video which talks about the exact same approach here

GraphQL Query with variables from data object

I have a graphQl query where I use a Prismic uid to get a specific user. Now I also want to get a user space from another prismic set of data called prismicSpaces but instead of looking for the data based on the uid on prismicSpaces I want to pass it an identifier from prismicUsers data object called location_id. Seen as location_id is not at the root like uid how can I use that to pass it down to prismicSpaces ?
below is my current query which works great but I need a way to pass on the location_id like the second example below this.
ps. I'm using gatsby and prismic
export const query = graphql`
query UsersQuery($uid: String!) {
page: prismicUsers(uid: { eq: $uid }) {
uid
type
data {
location_id
}
}
}
spaces: prismicSpaces {
uid
data {
name
}
}
}
`;
Example but this does not work.
export const query = graphql`
query UsersQuery($uid: String!, $space_id:String! ) {
page: prismicUsers(uid: { eq: $uid }) {
uid
type
data {
space_id
}
}
}
spaces: prismicSpaces(uid: {eq: { $space_id }) {
uid
data {
name
}
}
}
`;

Resources