Skip to content

suspense useQuery infinite loop when query goes from success to false and query key changes #2187

@flybayer

Description

@flybayer

Describe the bug
The workaround in #2157 fixed only part of the issue we are having in Blitz. We still have a case on user logout where we get infinite loops.

Failing test case included below. They key thing that makes it go into the infinite loop is the query key changing. If the key doesn't change, then it works fine.

To Reproduce

Add the following test to src/react/tests/suspense.test.tsx

  it('should not have infinite loop', async () => {
    const key = queryKey()

    const consoleMock = mockConsoleError()

    let succeed = true

    function Page() {
      const [nonce] = React.useState(0)
      const fullKey = `${key}-${succeed}`
      const result = useQuery(
        fullKey,
        async () => {
          await sleep(10)
          console.log('query', fullKey)
          if (!succeed) {
            throw new Error('Suspense Error Bingo')
          } else {
            return nonce
          }
        },
        {
          retry: false,
          suspense: true,
        }
      )
      return (
        <div>
          <span>rendered</span> <span>{result.data}</span>
          <button
            aria-label="fail"
            onClick={async () => {
              await queryClient.resetQueries()
            }}
          >
            fail
          </button>
        </div>
      )
    }

    function App() {
      const { reset } = useQueryErrorResetBoundary()
      return (
        <ErrorBoundary
          onReset={reset}
          fallbackRender={({ resetErrorBoundary }) => (
            <div>
              <div>error boundary</div>
              <button
                onClick={() => {
                  resetErrorBoundary()
                }}
              >
                retry
              </button>
            </div>
          )}
        >
          <React.Suspense fallback="Loading...">
            <Page />
          </React.Suspense>
        </ErrorBoundary>
      )
    }

    const rendered = renderWithClient(queryClient, <App />)

    await waitFor(() => rendered.getByText('Loading...'))
    await waitFor(() => rendered.getByText('rendered'))

    succeed = false
    fireEvent.click(rendered.getByLabelText('fail'))
    await waitFor(() => rendered.getByText('error boundary'))
    consoleMock.mockRestore()
  })

Version

React-query 3.13.9

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions