Skip to content

prefetchInRender causes infinite render loop with deferred value if a promise rejects #8249

Open
@ali-idrizi

Description

@ali-idrizi

Describe the bug

When the query of a deferred component rejects, it falls into an infinite render loop. This is consistent and I have written a test case.

I was waiting to see if this was being caused by #8219, but it appears not. Here's the test:

it.only('should throw error if query fails with deferred value', async () => {
  function MyComponent(props: { promise: Promise<string> }) {
    const data = React.use(props.promise)

    return <>{data}</>
  }

  const key = queryKey()
  let renderCount = 0

  function Page() {
    renderCount++

    const [_count, setCount] = React.useState(0)
    const count = React.useDeferredValue(_count)

    const query = useQuery({
      queryKey: [key, count],
      queryFn: async () => {
        await sleep(1)
        // succeed only on first query
        if (count === 0) {
          return 'test' + count
        }
        throw new Error('Error test')
      },
      retry: false,
    })

    return (
      <React.Suspense fallback="loading..">
        <button onClick={() => setCount((curr) => curr + 1)}>inc</button>
        <MyComponent promise={query.promise} />
      </React.Suspense>
    )
  }

  const rendered = renderWithClient(
    queryClient,
    <ErrorBoundary fallbackRender={() => <div>error boundary</div>}>
      <Page />
    </ErrorBoundary>,
  )

  await waitFor(() => rendered.getByText('loading..'))
  await waitFor(() => rendered.getByText('test0'))

  const consoleMock = vi
    .spyOn(console, 'error')
    .mockImplementation(() => undefined)

  fireEvent.click(rendered.getByText('inc'))

  await waitFor(() => rendered.getByText('error boundary'))

  consoleMock.mockRestore()

  // unsure what the exact render count should be, but it should be less than 10
  // it's 6 without `useDeferredValue`, when test passes
  expect(renderCount).toBeLessThan(10)
})

It currently fails waiting for the error boundary because of the infinite render. If you catch the waitFor throw, you will notice very large values for renderCount:

AssertionError: expected 5828 to be less than 10

The same issue occurs when removing useDeferredValue, and updating the count within a startTransition instead.

Your minimal, reproducible example

Failing test provided

Steps to reproduce

N/A

Expected behavior

N/A

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

OS: Windows 11
Browser: Chrome
Version: 129.0

Tanstack Query adapter

react-query

TanStack Query version

06e315c

TypeScript version

No response

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions