Can't pass arguments in useQuery with React-Query + Graphql-Request - reactjs

I'm a bit stuck. I am using graphql-request with react-query, following the example code quite closely. I replaced it with my own backend, and it works when I don't use variables, but hardcode the values. It also all works in my GraphiQL test environment.
So doing this works:
export default function usePost(postId) {
return useQuery(
["post", postId],
async () => {
const { post } = await request(
endpoint,
gql`
query {
post(id: "123123123123") { // <--- with the id hard-coded, it works
id
heading
content
}
}
`
)
return post
},
{
enabled: !!postId,
}
)
}
What follows is exactly the same code, but now the previously hard-coded post-id ("123123123123") is replaced by a variable (${postId}). Exactly as described in the example code
export default function usePost(postId) {
return useQuery(
["post", postId],
async () => {
const { post } = await request(
endpoint,
gql`
query {
post(id: ${postId}) { // <--- with the ${postId} variable, things break, but it's exactly the same syntax as in the react-query example & it works in my graphiql backend. Also console-logged the postId, and it is correct
id
heading
content
}
}
`
)
return post
},
{
enabled: !!postId,
}
)
}
The error response is:
commons.js:46154 Error: Syntax Error: Expected :, found ):
{"response":{"errors":[{"message":"Syntax Error: Expected :, found
)","locations":[{"line":3,"column":46}]}],"status":400},"request":{"query":"\n
query {\n post(id: 5fda109506038d9d18fa27e2) {\n
id\n heading\n content\n }\n
}\n "}}
at GraphQLClient. (commons.js:13039)
at step (commons.js:12930)
at Object.next (commons.js:12911)
at fulfilled (commons.js:12902)
I guess it's some syntax that I am getting wrong? Or could it have to do with the fact that now the quotation marks are missing? Though the example code also doesn't do anything differently... Really not sure anymore, but it's literally that one line that breaks it all and that I cannot figure out.

Your id 5fda109506038d9d18fa27e2 looks to be a string but you're not passing it as a string to your back end, which is why you're getting a syntax error.
It looks like this
query {
post(id: 5fda109506038d9d18fa27e2) {
id
title
body
}
}
Notice how there aren't any quotation marks around the id? e.g. "5fda109506038d9d18fa27e2". You can also use integers as ids, I just want to make a point that you're not actually passing an integer. Read more on scalar types here.
I recommend you pass variables how they're intended by graphql rather than using string interpolation. This will help avoid this problem. Read more on variables in graphql here.
Here's an example of passing variables in graphql:
query Post($id: ID!) {
post(id: $id) {
id
title
body
}
}
Here's how it would look using your code:
function usePost(postId) {
return useQuery(
["post", postId],
async () => {
const { post } = await request(
endpoint,
gql`
query Post($id: ID!) {
post(id: $id) {
id
title
body
}
}
`,
{ id: postId }
);
return post;
},
{
enabled: !!postId
}
);
}

Related

remove undefined from graphql rest query

I'm using graphql all across my app. The problem is on the front end I also have to make a query to a 3rd party rest API and to remain consistent I used #rest directive of graphql to make a get request. Now the issue is, I want graphql to not send the query params that are undefined but the issue is with #rest directive it sends the undefined value too which is becoming an issue for me. Can somebody please tell me how can I fix this? Thanks
const getNftsQuery = gql`
query Nfts($limit: Int, $owner: String!) {
# eslint-disable-next-line #graphql-eslint/fields-on-correct-type
entities: nfts(limit: $limit, owner: $owner)
# eslint-disable-next-line #graphql-eslint/known-directives
#rest(method: GET, path: "/api/v1/assets?owner={args.owner}&limit={args.limit}&cursor={args.cursor}", type: "[Nft]") {
list: assets {
id
image_url
name
token_id
}
}
}
`;
function useGetNfts(owner: string, limit: number, cursor: string | undefined): QueryNftsResults {
const { loading, error, data, networkStatus, fetchMore } = useQuery<Pagination<NFT>>(getNftsQuery, {
variables: { owner, limit, cursor },
notifyOnNetworkStatusChange: true,
context: { clientName: ClientName.nftClient },
});
return { loading, error, data, networkStatus, fetchMore };
}
export default useGetNfts;

Passing ID in GraphQL query not returning data

so i'm trying to use Apollo GraphQL with React to get specific product data by its ID, but it seems to be returning undefined. I read the Apollo docs and researched, so I'm not sure what I'm doing wrong. Also, I'm able to return data from other queries that don't require an ID (like all products, for instance). Would greatly appreciate some help!
Query
export const PRODUCT = gql`
query GetProduct($itemID: String!) {
product(id: $itemID) {
id
name
inStock
gallery
description
category
attributes {
id
name
type
items {
displayValue
value
}
}
prices {
currency {
label
symbol
}
}
brand
}
}
`;
This is where I try to return data using the ID, but to no avail:
let myID = "ps-5";
const { productLoading, productError, productData } = useQuery(PRODUCT, {
variables: { itemID: myID },
});
useEffect(() => {
if (productData) {
console.log("data: " + productData) // logs nothing. "Undefined" when if statement is removed
}
}, [])
It looks like the React client for Apollo uses the same API for useQuery as for Vue (with which I'm more familiar), in which case it should be used like this:
useQuery(PRODUCT, { itemID: myID })
(not { variables : { itemID : myID }})
I would have expected the backend to return an error though, because $itemID is declared as non-nullable.
It seems that you are destructing the object that useQuery() returns with the wrong object keys.
// instead of
const { productLoading, productError, productData } = '...'
// you can either use the regular keys as variables
const { loading, error, data } = '...'
// or assign aliases (useful when you use more queries on the same page)
// this way you can use the same variables as in your example
const { loading:productLoading, error:productError, data:productData } = '...'

ReactJS : import GraphQL queries dynamically based on a State

I query a specific variable called nameTranslated from my schema, It takes the parameter of the locale with is En-CA, Fr-FA etc and gets the desired word in french. And the way I handle this in my frontend reactjs application is like this:
export const App = () => {
const { locale } = useIntl()
const LOAD_TABLE = gql`
query getItems($id: String!) {
Items(id: $id) {
id
notes
nameTranslate(language:"${l}")
defaultClass {
nameTranslate(language:"${l}")
}
}
}
`
useEffect(() => {
// a function to fetch LOAD_TABLE
},[locale])
}
The above code works perfectly fine and whenever I change the locale variable it re fetches the query. But the problem with this is i have many other query I need to work with, my file length becomes too long and hard to manage. At the same time if I pull the file out, I lose the privilage of dynamacally adding a type for nameTranslate.. How can I solve this issue?
You can make it more modular but still dynamic by using custom hooks, for example:
// hooks/useItemsQuery.js
function useItemsQuery(locale) {
const itemsQuery = useMemo(() => gql`
query getItems($id: String!) {
Items(id: $id) {
id
notes
nameTranslate(language:"${locale}")
defaultClass {
nameTranslate(language:"${locale}")
}
}
}
`, [locale])
return itemsQuery
}
// App.js
export const App = () => {
const { locale } = useIntl()
const itemsQuery = useItemsQuery(locale)
useEffect(() => {
// a function to fetch itemsQuery
}, [itemsQuery])
}
Or if you need to call outside of React just a normal function will do. I think this won't have a perf impact as es6 tpl literals are cached (the gql`` part) as long as the variables don't change, even inside a function. If that's the case the use of useMemo above is redundant anyway.
function itemsQuery(locale) {
return gql`
query getItems($id: String!) {
Items(id: $id) {
id
notes
nameTranslate(language:"${locale}")
defaultClass {
nameTranslate(language:"${locale}")
}
}
}
`
}

ApolloClient v3 fetchMore with nested query results

I'm using ApolloClient 3 the GitHub GraphQL API to retrieve all releases from a repo.
This is what the query looks like:
query ($owner: String!, $name: String!, $first: Int, $after: String, $before: String) {
repository(owner: $owner, name: $name) {
id
releases(orderBy: {field: CREATED_AT, direction: DESC}, first: $first, after: $after, before: $before) {
nodes {
name
publishedAt
resourcePath
tagName
url
id
isPrerelease
description
descriptionHTML
}
totalCount
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
}
This is what the result payload looks like:
This returns me the first x entries (nodes). So far, all good.
I need to implement pagination and I make use of the fetchMore function provided by ApolloClient useQuery. Calling fetchMore fetches the next x entries successfully but these are not displayed in my component list.
According to the ApolloClient Pagination documentation, it seems necessary to handle the merging of the fetchMore results with the ApolloClient caching mechanism. The documentation is understandable for simple situations but I am struggling to implement a solution for the situation where the actual array of results that needs to be merged togeher is deeply nested in the query result (repository -> releases -> nodes).
This is my implementation of the InMemoryCache options merge:
const inMemoryCacheOptions = {
addTypename: true,
typePolicies: {
ReleaseConnection: {
fields: {
nodes: {
merge(existing, incoming, options) {
const previous = existing || []
const results = [...previous, ...incoming]
return results
}
}
}
},
}
}
The results array here contains the full list, including the existing entries and the new x entries. This is essentially the correct result. However, my component list which is using the useQuery and fetchMore functionality does not get the new entries after the fetchMore is called.
I have tried various combinations in the inMemoryCacheOptions code above but so far I have been unsuccessful.
To add more context, this is the related component code:
export default function Releases() {
const { loading, error, data, fetchMore } = useQuery(releasesQuery, {
variables: {
owner: "theowner",
name: "myrepo",
first: 15
}
});
if (loading) return null;
if (error) {
console.error(error);
return null;
}
if (data) {
console.log(data?.repository?.releases?.pageInfo?.endCursor);
}
const handleFetchMore = () => {
fetchMore({
variables: {
first: 15,
after: data?.repository?.releases?.pageInfo?.endCursor
}
});
};
return (
<div>
<ul>
{data?.repository?.releases?.nodes?.map(release => (
<li key={release.id}>{release.name}</li>
))}
</ul>
<button onClick={handleFetchMore}>Fetch More</button>
</div>
);
}
After fetchMore the component doesn't rerender with the new data.
If anyone has any other ideas that I could try, I'd be grateful.
I finally managed to solve this. There was no change to the react component code but the InMemoryCacheOptions now looks like this:
const inMemoryCacheOptions = {
addTypename: true,
typePolicies: {
Repository: {
fields: {
releases: {
keyArgs: false,
merge(existing, incoming) {
if (!incoming) return existing;
if (!existing) return incoming;
const { nodes, ...rest } = incoming;
// We only need to merge the nodes array.
// The rest of the fields (pagination) should always be overwritten by incoming
let result = rest;
result.nodes = [...existing.nodes, ...nodes];
return result;
}
}
}
}
}
};
The main change from my original code is that I now define the typePolicy for the releases field of the Repository type. Previously I was trying to get directly to the nodes field of the Release type. Since my Repository type the root of the gql query and used in the component, it now reads the merged results from the cache.
If I specified the typePolicy for Query as mentioned in the docs, I would not be able to specify the merge behaviour for the releases field because it would be one level too deep (i.e. Query -> repository -> releases). This is what lead to my confusion in the beginning.

Apollo Client is not reading variables passed in using useQuery hook

Having a weird issue passing variables into the useQuery hook.
The query:
const GET_USER_BY_ID= gql`
query($id: ID!) {
getUser(id: $id) {
id
fullName
role
}
}
`;
Calling the query:
const DisplayUser: React.FC<{ id: string }> = ({ id }) => {
const { data, error } = useQuery(GET_USER_BY_ID, {
variables: { id },
});
return <div>{JSON.stringify({ data, error })}</div>;
};
Rendering the component:
<DisplayUser id="5e404fa72b819d1410a3164c" />
This yields the error:
"Argument \"id\" of required type \"ID!\" was provided the variable \"$id\" which was not provided a runtime value."
Calling the query from GraphQL Playground returns the expected result:
{
"data": {
"getUser": {
"id": "5e404fa72b819d1410a3164c",
"fullName": "Test 1",
"role": "USER"
}
}
}
And calling the query without a variable but instead hard-coding the id:
const GET_USER_BY_ID = gql`
query {
getUser(id: "5e404fa72b819d1410a3164c") {
id
fullName
role
}
}
`;
const DisplayUser: React.FC = () => {
const { data, error } = useQuery(GET_USER_BY_ID);
return <div>{JSON.stringify({ data, error })}</div>;
};
Also returns the expected result.
I have also attempted to test a similar query that takes firstName: String! as a parameter which also yields an error saying that the variable was not provided a runtime value. This query also works as expected when hard-coding a value in the query string.
This project was started today and uses "apollo-boost": "^0.4.7", "graphql": "^14.6.0", and "react-apollo": "^3.1.3".
[Solved]
In reading through the stack trace I noticed the issue was referencing graphql-query-complexity which I was using for validationRules. I removed the validation rules and now everything works! Granted I don't have validation at the moment but at least I can work from here. Thanks to everyone who took the time to respond!
I had also ran into a similar issue and was not really sure what was happening.
There seems to be similar problem reported here - https://github.com/apollographql/graphql-tools/issues/824
We have 2 options to fix the issue.
- First one is a simple fix, where in you don't make the ID mandatory when it takes only a single parameter ( which is not an object )
const GET_USER_BY_ID= gql`
query($id: ID) {
Second option is to use an object as a parameter instead of a primitive. I went ahead with this and it seemed to work fine for me even though I made the object and the property inside to be required.
// On the client
const GET_USER_BY_ID= gql`
query($input: GetUserInput!) {
getUser(input: $input) {
id
fullName
role
}
}`;
const { data, error } = useQuery(GET_USER_BY_ID, {
variables: { input: { id }},
});
// In the server, define the input type
input GetUserInput {
id: ID!
}
Try
const { data, error } = useQuery(GET_USER_BY_ID, { id });

Resources