react-apollo
react-apollo copied to clipboard
useQuery infinite loop re-rendering
Intended outcome:
The useQuery hook fires. If the GQL results in error, I use a library to raise a toast notification inside of Apollo client's onError callback. This should not cause functional component to re-render.
Actual outcome:
The functional component re-renders. The useQuery hook issues another network request. Apollo client's onError callback runs, another toast fires. Resulting in infinite loop of network request, error, and toast.
This might be similar to the class-based components infinite loop problem that could occur when a component's render method invokes setState resulting in infinite loop of re-renders
How to reproduce the issue:
Following code example works: https://codesandbox.io/s/blissful-hypatia-9qsoy?fontsize=14
The below GQL is valid and works as I intend:
const GET_ITEMS = gql`
{
continents {
name
}
}
`;
but if you were to alter the GQL to something invalid as shown below, the infinite loop issue occurs:
const GET_ITEMS = gql`
{
invalidGQL {
name
}
}
`;
Could be related to #3595
yup, there's even test for that https://github.com/apollographql/react-apollo/blob/master/packages/hoc/src/tests/queries/lifecycle.test.tsx#L448-L665
Having the same problem. The infinite loop keeps on going even when the component containing the useQuery
is unmounted. I have a pollInterval
on my query and the query is rerun at that rate.
@ostrebler pollInterval
causes refetches on top of the already existing re-rendering problem. Disable it to see for yourself.
I had this also. I solved it with replacing the the useQuery hook with the query component and the behavior was gone
Hi, I also have an infinite loop on error. In my case removing <React.StrictMode>
has stopped this issue
Has someone solved this problem? I also have the same issue
Same here, except I have an infinite loop even on a successful response !
My component keeps re-rendering once my graphQL query has successfully returned a response.
This re-render fires off the query again, and once the query has returned the data the component is re-rendered.
And this keeps on going infinitely.
Changing the poll interval to 0 does not fix the issue.
Here is my usage of the useQuery hook in my functional component:
const {data: cataloguePageCount, loading: pageCountLoading} = useQuery(getCataloguePageCount, { client: apiClient, });
And my graphql query:
query getCataloguePageCount { pageCount: getCataloguePageCount }
I'm having this issue as well. For me, this was caused by having a query variable that changes on every render - I have a timestamp as a variable that is determined relative to now. Rounding the timestamp to the nearest minute stopped the loop for me.
Any updates on this? Same problem here. Getting an infinite re-rendering whenever the query fires on click.
I also noticed this issue using loader
of graphql.macro
to load .graphql files instead of using gql
from graphql-tag
.
I found a solution of my infinite loop issue, just passing an empty function to onCompleted
property of useQuery
and problem solved 😕
import React from "react";
import {FormattedMessage} from 'react-intl';
import {loader} from 'graphql.macro';
import {useQuery} from '@apollo/react-hooks';
const Workspace = () => {
const GET_WORKSPACE = loader('./../../../graphql/Workspace/get_workspace.graphql');
const {loading, error, data, refetch} = useQuery(
GET_WORKSPACE,
{
variables: {user_id: "12345"},
onCompleted: data => { }
}
);
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
return (
<div style={{padding: 24, background: '#fff', minHeight: 360}}>
<h2><FormattedMessage id="dashboard.tasks.title" defaultMessage="Tasks"/></h2>
{data.workspace.map(item => (
<span key={item.id}> {item.name}</span>
))}
<button onClick={() => refetch()}>Refetch!</button>
</div>
);
};
export default Workspace;
It's probably problem issued by graphql.macro
and any other GraphQL loaders (like babel-plugin-graphql-tag
in my case). This plugins compiles GraphQL query to static object, which will be recreated every re-render cycle. To prevent this behaviour you can do following:
- extract query declaration from render function
const GET_WORKSPACE = loader('./../../../graphql/Workspace/get_workspace.graphql'); const Workspace = () => { const {loading, error, data, refetch} = useQuery(GET_WORKSPACE, { variables: {user_id: "12345"} }); // continue rendering };
- use memoization
const Workspace = () => { const GET_WORKSPACE = React.useMemo(() => loader('./../../../graphql/Workspace/get_workspace.graphql'), []); const {loading, error, data, refetch} = useQuery(GET_WORKSPACE, { variables: {user_id: "12345"} }); // continue rendering };
It's probably problem issued by
graphql.macro
and any other GraphQL loaders (likebabel-plugin-graphql-tag
in my case). This plugins compiles GraphQL query to static object, which will be recreated every re-render cycle. To prevent this behaviour you can do following:
- extract query declaration from render function
const GET_WORKSPACE = loader('./../../../graphql/Workspace/get_workspace.graphql'); const Workspace = () => { const {loading, error, data, refetch} = useQuery(GET_WORKSPACE, { variables: {user_id: "12345"} }); // continue rendering };
- use memoization
const Workspace = () => { const GET_WORKSPACE = React.useMemo(() => loader('./../../../graphql/Workspace/get_workspace.graphql'), []); const {loading, error, data, refetch} = useQuery(GET_WORKSPACE, { variables: {user_id: "12345"} }); // continue rendering };
Thanks, @mixkorshun that solve my issue with graphql.macro
😎🙌🏻
I'm having this issue as well. For me, this was caused by having a query variable that changes on every render - I have a timestamp as a variable that is determined relative to now. Rounding the timestamp to the nearest minute stopped the loop for me.
I had this same issue, using a timestamp in a function that built the query caused it to re-render every time. I also saw this happen on failure though.
Guys, I'm not sure., But I had similar issue with an infinite loop and in my case, it helped for me.
This is a copy of my answer from Stack Overflow: https://stackoverflow.com/questions/59660178/overcome-endless-looping-when-executing-usequery-apolloclient-by-defining-a-ne/61817054#61817054
I just exclude new ApolloClient
from render function.
Actually, I don't see render function from your code, but in my case, it was something like this:
Before
export default function MyComponent () {
const anotherClient = new ApolloClient({
uri: "https://my-url/online-service/graphql"
});
const { data, loading } = useQuery(QueryKTP, {client: anotherClient});
}
After
const anotherClient = new ApolloClient({
uri: "https://my-url/online-service/graphql"
});
export default function MyComponent () {
const { data, loading } = useQuery(QueryKTP, {client: anotherClient});
}
In my case, it helped. You should know, that in similar cases just look to new keyword. For example, guys often meet the same bug with an infinite loop, when they use new Date() in the render function
I'm having a similar problem, and did find a temporary solution. But Oh-My, this was hard to trace down.
After upgrading @apollo/client
to the v3 range, my (local) server was being spammed by requests. Not directly though, it started after half a minute.
Turned out, it was a poll event that triggered it. I still don't know why or how, but I do know how to work around it.
The initial render went fine. It's only after the poll
is being triggered, that the query gets stuck in a loop. The component itself doesn't get rendered though. React doesn't paint any updates, and logging statements around the query aren't being written either.
const { data, refetch } = useMyQuery({
skip: !itemId,
variables: { itemId },
// pollInterval: 15 * 1000,
});
useEffect(() => {
const id = setInterval(() => {
refetch();
}, 15 * 1000);
return () => clearInterval(id);
}, [refetch]);
That's my workaround. If I uncomment pollInterval
and remove the useEffect
, the query will be thrown in that infinite loop after 15 seconds.
ps. I'm using graphql-code-generator.com to generate that useMyQuery
.
update
I've simplified my workaround to import useInterval
from beautiful-react-hooks
.
const { data, refetch } = useMyQuery({
skip: !itemId,
variables: { itemId },
});
useInterval(refetch, 15 * 1000);
Something seems to be broken inside the pollInterval
.
My workaround is also to leverage the "skip" property.
The code looks like below:
const [mydata,setMyData]=useState(null)
const {data, loading,error}=useQuery(MyQuery,{
variables:{itemId},
skip: !!mydata
})
if(data){
setMyData(data)
}
// use mydata to render UI
I face this issue too, with two queries actually.
First query is like that: { me { id name avatar authenticated } }
Second query is like that: { me { games {...GameInfo} } }
When application opens, the first query is executed once, everything works fine.
As soon as we click on the menu to see the games, the second query runs... and there goes the trouble: it causes the first query to run again, which causes the second to run again, etc.
I guess it relates to some cache invalidation, but I request complementary fields of the same query. Is it the intended behavior? I don't want to query the user's game in the same query because it requires database access and makes things too slow at startup.
Edit: just upgraded from @apollo/client 3.0.0-beta.50 to 3.0.0-rc.10 and bug is solved apparently. Now it only triggers the first query once (which I don't need, however it causes no more harm)
I had this problem when I use fetchPolicy
, with useQuery
and useLazyQuery
. I just remove from arguments. Work for me.
I don't know why are with this problem now.
When i delete (or im not using) query option "OnError" or "OnComplete" i dont have loop problem... but when one or any other is present then infinity render loop is available.. @benjamn is this going to be planned to fix?
I found another related same topics.. Im adding hire to have in place for anothers...
https://github.com/apollographql/apollo-client/issues/6634 https://github.com/apollographql/react-apollo/issues/4044 https://github.com/apollographql/react-apollo/issues/4000