`useLazyQuery` From Apollo Seems To Trigger Infinite Render Loop - reactjs

I try to write a Shopify component using Next.js and the Apollo Client. This is my cart component, which always enters an infinite loop (hundreds of renders per second):
import { gql, useLazyQuery } from "#apollo/client"
export default function Cart() {
const checkoutCreateMutation = gql`
mutation checkoutCreate($input: CheckoutCreateInput!) {
checkoutCreate(input: $input) {
checkout {
id
}
}
}
`
const cartLineItemsQuery = gql`
query ($id: ID!) {
node(id: $id) {
id
}
}
`
const cartId = "MY_CART_ID"
const [getCartItems, data] = useLazyQuery(cartLineItemsQuery)
getCartItems({ variables: { id: cartId } })
return (
<p>This is your data: {JSON.stringify(data)}</p>
)
}
I suspect that my queries may be wrongly formatted, but I'd still like to have an error thrown in this case instead of the infinite loop. Or could the problem also be located outside of that component? When I delete the getCartItems(...) line, no infinite loop occurs.
The reason I use useLazyQuery instead of useQuery is that this code is already reduced, originally I first look if a cart ID exists in the cache and if this is not the case I request a new cart ID, otherwise I query the content of the cart.

I misunderstood the idea behing useLazyQuery. It functions more similar to useMutation, where the execution triggers a rerendering. In my case, the working solution was to use useQuery with a skip parameter, so the query will only execute if, e.g., the cart ID variable is defined.

Related

How to refresh graphql data on state change

import { useQuery, gql, useMutation } from "#apollo/client";
const Questions = () => {
const [modal, setModal] = useState(false)
const QUESTION_QUERIES = gql`
query getQuestions(
$subjectRef: ID
$gradeRef: ID
$chapterRef: ID
$status: String
) {
getQuestions(
subjectRef: $subjectRef
gradeRef: $gradeRef
chapterRef: $chapterRef
status: $status
) {
id
question_info
question_type
answer
level
published
subjectRef
gradeRef
chapterRef
levelRef
streamRef
curriculumRef
options
status
subject
grade
chapter
stream
curriculum
}
}
`;
const { loading, error, data } = useQuery(QUESTION_QUERIES);
return (
<div>
</div>
)
}
Here is my react graphql code.
I wants to fetch data when modal change using state if modal status change to true to false or false to
true it will make api call to fetch questions again
Please take a look how to solve the issue.
use useLazyQuery:
const [updateFn,{ loading, error, data }]= useLazyQuery(QUESTION_QUERIES);.
Then create useEffect with modal as dependency variable, and call updateFn inside useEffect
You want to fetch data after the modal state change, So you simply use useEffect and put modal in the dependency list of the useEffect and for useQuery there is also a function called refetch, the logic would be like this
const { loading, error, data, refetch } = useQuery(QUESTION_QUERIES);
useEffect(() => {
// the reason I put if condition here is that this useEffect will
// also run after the first rendering screen so you need to put a check
// to do not run refetch in that condition
if (data) refetch();
}, [modal]);

Why is useQuery in apollo client not actually getting the data?

I am having strange situation with React routing with GraphQL. I have a users in /users url and when i go to users page for the first time useQuery is working, request is going to the server and bringing me some data. Then, if i go to another page and come back to the users page, useQuery is not working and request is not going to the server. useQuery is working and request is going to the server when i reload the page. Here is my code and query for Users component:
Users.js
import React, { useState, useEffect } from "react";
import { useLazyQuery, useQuery } from '#apollo/client'
import { USERS} from './queries'
const [usersList, setUsersList] = useState([])
const { loading, error, data } = useQuery(USERS);
const Users= () => {
useEffect(() => {
const result = data?.users?.payload.filter(item => item.status === "waiting" && !item.isDeleted)
setUsersList(result)
}, [loading, data])
return (
<div>
{usersList?.map(user => (
<div key={user.key}>
<h1>{user.name}</h1>
<p>{user.status}</p>
</div>
))}
</div>
)
}
queries.js
import { gql } from '#apollo/client';
export const USERS= gql`
query{
users(size: 50){
payload{
key,
isDeleted,
status,
name,
...
}
}
}`
So, final question, why useQuery is working only one time when site is reloaded, not rerendered?
This is by design –– your query's results would have been stored in Apollo Client's cache the first time that query triggered. If the query's variables haven't changed, then the query won't actually travel the network again, Apollo Client will pull the data out of its cache instead.
You could change the fetchPolicy on your useQuery hook if you want the request to go over the network to your server every time your hook is run: https://www.apollographql.com/docs/react/data/queries/#supported-fetch-policies
You can use useLazyQuery and useEffect for that
useLazyQuery
const [runQuery, { data, loading, called }] = useLazyQuery(yourQuery);
useEffect(() => { runQuery(); }, [location]);
I think you will get the idea ;)
EDIT: also you can change FetchPolicy on the normal to network-only query (I think this should work too):
params of useQuery

to perform multiple dependent mutation - grandstack apollo graphql 3.3

I've a bit of confusion how to perform multiple mutation in graphql.
I've read articles about using of graphql() function or compose() function, but i haven't understood if these are the legacy way to do it (version 2)
or is still a valid way to proceed.
I'm currently using apollo 3.3 with grandstack and in my component i do the following to perform a mutation (i used useMutation() hook provided by ApolloClient)
const CreateFBUser = gql `
mutation CreateFBUser($name: String, $fbId: String!) {
CreateFBUser(name: $name, fbId: $fbId) {
_id
id
fbId
name
}
}
`
function FbUserForm() {
const { id } = useParams()
const [formState, setFormState] = useState({})
const [createFBUser,{data: createData}] = useMutation(CreateFBUser)
const { loading, error, data } = useQuery(FBUser,{
variables: {_id: id}
});
...
..
.
as you can see, i havent used components like <Query>, and so on..
FIRST QUESTION: is this component related to the apollo old version? are still regulary used or useMutation() hook is the first choice in apollo 3?
SECOND QUESTION: i need to perform a second mutation related to the first, and i need the generated id from the first mutation to execute the second
//the first mutation
const CreateFBUser = gql `
mutation CreateFBUser($name: String, $fbId: String!) {
CreateFBUser(name: $name, fbId: $fbId) {
_id
id
fbId
name
}
}
`
//the second mutation (pseudocode)
const AddFBUserMemberOf = gql`
mutation AddFBUserMemberOf($from: _GroupInput!, $to: _FBUserInput!) {
AddFBUserMemberOf(from: $from, to: $to) {
from
to
}
}
`
moreover, the second mutation should be performed conditionally according to a value/a variable/something else
The render prop components are deprecated and will not receive further updates or bug fixes according to the docs
For your second question; the mutation function returned from useMutation takes an onCompleted property in the options parameter that executes after the mutation successfully completes.
const [createFBUser,{data: createData}] = useMutation(CreateFBUser)
const [addFBUserMemberOf] = useMutation(AddFBUserMemberOf)
createFBUser({
variables: {
//...
},
onCompleted: (data) => {
// data contains the result of createFBUser
addFBUserMemberOf({
variables: {
//...
}
})
}
})

useQuery gql once from the db and then from the cache after the first request

time for a new question. Each page as a query simular like:
const {data} = useQuery(EXAMPLE)
export const EXAMPLE = gql`
query homePage(
$orderBy: PostOrderByInput
$userId: ID
) {
posts( //Individual query for each page
orderBy: $orderBy
userId: $userId
) {
id
title
}
userUserRelation{ //alway the same query. Need this request just once
id
}
}
`;
The posts query is individual. Always different variables for each page. The userUserRelation query is alway the same. I just need the userUserRelation once from the db, at the first visited page. Then I can query it from the cache.
Is there a possibility to make the posts request always from the db and the userUserRelation query just once from the db and then from the cache? Comparable like #client
Of course I can solve this with a global state. And make a extra gql without the userUserRelation after the fist request. Or I make a extra useQuery (But then I have 2 queries the fitst time...)
THX for any help.
You should split your query into two separate hooks. That way userUserRelation will only be fetched once as long as you use the default fetchPolicy of cache-first.
export const POSTS_QUERY = gql`
query Posts(
$orderBy: PostOrderByInput
$userId: ID
) {
posts(orderBy: $orderBy, userId: $userId) {
id
title
}
userUserRelation {
id
}
}
`;
export const USER_QUERY = gql`
query User {
userUserRelation {
id
}
}
`;
const { data: userData } = useQuery(USER_QUERY)
const { data: postsData } = useQuery(POSTS_QUERY, { variables: { ... } })
If you want the queries to be fetched inside a single request initially, you can enable batching for your client.

Apollo and React: refreshQueries not working in mutation fired from onClick()

I'm building a simple shopping app. On load, we'll make a query to check whether a cart exists. For now, that query always returns null (I haven't implemented logic yet to check whether a user has a shopping cart already). Then, when a user clicks "create cart", we do a mutation to create it. Finally, we'll use refreshQueries after the mutation finishes to fetch the cart (and its products) by ID, which is returned from the mutation. We then render those products in a pure component.
The problem I'm encountering is that nothing re-renders after users click the button and refreshQueries happens. I know the query is being sent and returning a created cart with products by looking at my developer tools' network tab. It's just that Apollo doesn't seem to notice the change.
Mongo is my back-end.
Here's the relevant code:
// query
import { gql } from 'react-apollo';
export const cartQuery = gql`
query CartQuery($cartId: ID) {
cart(cartId: $cartId) {
_id,
products {
_id,
name
}
}
}
`;
// mutation
import { gql } from 'react-apollo';
export const createCartMutation = gql`
mutation CreateCartMutation {
createCart {
_id
}
}
`;
// Apollo + React stuff
import React from 'react';
import { graphql } from 'react-apollo';
import { createCartMutation } from '../../mutations';
import { cartQuery } from '../../queries';
const BuildCart = ({ mutate }) => (
<button
onClick={() => {
// even when I hardcode the ID of a cart that exists and contains products, it doesn't matter
mutate({
refetchQueries: [{ query: cartQuery, variables: { cartId: '12345abcd' } }],
});
}}
>
Click
</button>
);
const BuildCartConnected = graphql(createCartMutation)(BuildCart);
const Cart = ({ data, data: { cart, loading } }) => {
console.log(cart); // always null, even after clicking button
return (
<div>
<BuildCartConnected />
{loading && <p>Loading...</p>}
// never gets to this step, even though the response is a properly formed cart with an array of products
{cart && cart.products.map(product => (
<p key={Math.random()}>{product.name}</p>
))}
</div>
);
};
const CartConnected = graphql(cartQuery)(Cart);
export default CartConnected;
If it's helpful, here's what the response from refetchQueries looks like in the network tab:
{"data":{"cart":{"_id":"12345abcd","products":[{"_id":"abcdef12345","name":"Product A","__typename":"Product"}],"__typename":"Cart"}}}
Your cart query takes a cartId variable. In the code above, you're not providing that variable, so its value is undefined. Apollo associates that combination of query plus variable(s) with your Cart component.
When you call refetch queries with a different variable than what is provided to the HOC, the results of that new query are fetched and persisted in the store. However, as far as Apollo knows, you still want the results of a query with undefined as the variable value for that component, not this new result you had it fetch.
refetchQueries shouldn't be utilized for what you're trying to do. Instead, the variable for the cart query should be derived from props:
const options = ({cartId}) => ({variables: {cartId}})
const CartConnected = graphql(cartQuery, {options})(Cart)
Then, when you call mutate, it returns a Promise that will resolve to the data returned by the mutation. We can grab the cartId from the response and store it within application state (by calling setState, firing off a Redux action, etc.). You can then pass that state down to your Cart component as a prop. When the state is updated, the prop changes and the query with the newly provided variable is fetched.

Resources