Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

useQuery infinite loop re-rendering #3644

Open
chanm003 opened this issue Oct 30, 2019 · 20 comments
Open

useQuery infinite loop re-rendering #3644

chanm003 opened this issue Oct 30, 2019 · 20 comments

Comments

@chanm003
Copy link

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
    }
  }
`;
@chanm003 chanm003 changed the title useQuery infinite loop useQuery infinite loop re-rendering Oct 30, 2019
@dylanwulf
Copy link
Contributor

Could be related to #3595

@strblr
Copy link

strblr commented Nov 1, 2019

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.

@PaulRBerg
Copy link

@ostrebler pollInterval causes refetches on top of the already existing re-rendering problem. Disable it to see for yourself.

@zerocity
Copy link

I had this also. I solved it with replacing the the useQuery hook with the query component and the behavior was gone

@mlecoq
Copy link

mlecoq commented Dec 23, 2019

Hi, I also have an infinite loop on error. In my case removing <React.StrictMode> has stopped this issue

@alexnofoget
Copy link

Has someone solved this problem? I also have the same issue

@danielmaartens-sovtech
Copy link

danielmaartens-sovtech commented Jan 21, 2020

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 }

@yuzhenmi
Copy link

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.

@kevloves
Copy link

kevloves commented Feb 4, 2020

Any updates on this? Same problem here. Getting an infinite re-rendering whenever the query fires on click.

@jesuslopezlugo
Copy link

jesuslopezlugo commented Feb 12, 2020

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;

@mixkorshun
Copy link

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
     };

@jesuslopezlugo
Copy link

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
     };

Thanks, @mixkorshun that solve my issue with graphql.macro 😎🙌🏻

@gregorskii
Copy link

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.

@KamilOcean
Copy link

KamilOcean commented May 15, 2020

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

@smeijer
Copy link

smeijer commented May 20, 2020

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.

@ilovepumpkin
Copy link

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

@fromi
Copy link

fromi commented Jul 4, 2020

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)

@Tautorn
Copy link

Tautorn commented Jul 16, 2020

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.

@damikun
Copy link

damikun commented Jul 17, 2020

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...

apollographql/apollo-client#6634
#4044
#4000

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests