Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maximum update depth exceeded #8677

Open
yWilliamLima opened this issue Feb 19, 2025 · 16 comments
Open

Maximum update depth exceeded #8677

yWilliamLima opened this issue Feb 19, 2025 · 16 comments

Comments

@yWilliamLima
Copy link

yWilliamLima commented Feb 19, 2025

Describe the bug

I am getting the error "Maximum update depth exceeded"

Your minimal, reproducible example

https://stackblitz.com/~/github.com/yWilliamLima/test-error

Steps to reproduce

It usually occurs after a while of running whenever changing pages/routes

Expected behavior

Do not return this error

How often does this bug happen?

Sometimes

Screenshots or Videos

Image
Image

Platform

OS: Windows 11
Browser: Chrome
Node: 22.12.0

Tanstack Query adapter

None

TanStack Query version

5.66.5

TypeScript version

5.7.3

Additional context

The error in the console does not occur locally, only in stackblitz
Maybe because of this error in the stackblitz console it doesn't happen, because it gives a refresh
Because it is a minimal example, it takes a while to occur, but in my project it occurs frequently

@tariqkb
Copy link

tariqkb commented Feb 19, 2025

I started getting this error after upgrading as well. I narrowed it down to version v5.66.4 introducing this behavior.

@yWilliamLima
Copy link
Author

I started getting this error after upgrading as well. I narrowed it down to version v5.66.4 introducing this behavior.

Your test is very good, the problem was actually introduced in v5.66.4, I downgraded to v5.66.3 and the problem does not occur

@TkDodo
Copy link
Collaborator

TkDodo commented Feb 19, 2025

@juliusmarminge 5.66.4 was our hydration changes 😕

@joseph0926
Copy link
Contributor

I think the following part of the logic added by #8383 in the PR might be causing some problems.

https://github.com/TanStack/query/pull/8383/files#diff-771e81c633f399ddff578509c1355021bff3ce55afb12d281cb9bc110842665fR83-R86

This is because the code in the above link thinks that the new logic is new if the “promise.status” is different.

But https://github.com/TanStack/query/pull/8383/files#diff-da66ebb93eacffd07c6ea6e697db57d6091050af95da336ea10e1aaedb07ef7cR86 is chasing a “promise”, so I think a new “promise” will be created each time.
(or is the “promise.status” actually different each time?)

Wouldn't that cause “Maximum update depth exceeded” to be thrown because the new code thinks it's new every time?

@juliusmarminge
Copy link
Collaborator

can anyone provide a minimal repro? I can look into this tonight but would help to have something simple to debug

@yWilliamLima
Copy link
Author

yWilliamLima commented Feb 21, 2025

I did some validations in my personal project where the error occurs frequently, where if I export the Page as async and use await in prefetchQuery the problem does not occur:

export default async function Page() {
  const queryClient = getQueryClient()

  await queryClient.prefetchQuery({ ...getApiV1ActivitiesOptions() })

@juliusmarminge
Copy link
Collaborator

yea but then you're no longer sending promises down so you're hitting completely different code paths with that - not really comparable

@juliusmarminge
Copy link
Collaborator

I just want a state where it's failing on that PR I linked 😅

@joseph0926
Copy link
Contributor

@juliusmarminge

A simple way to reproduce it is to add an artificial delay to the “getApiV1ActivitiesOptions” function in your temporary repo PR(#8682 ) in the file “integrations/react-next-15/app/query.ts” (await new Promise((r) => setTimeout(r, 5000)))
If you comment and check back later when you're patching the delay, you should see an infinite render.

@joseph0926
Copy link
Contributor

joseph0926 commented Feb 21, 2025

@juliusmarminge

A simple way to reproduce it is to add an artificial delay to the “getApiV1ActivitiesOptions” function in your temporary repo PR(#8682 ) in the file “integrations/react-next-15/app/query.ts” (await new Promise((r) => setTimeout(r, 5000))) If you comment and check back later when you're patching the delay, you should see an infinite render.

(I used a translator)
I don't know if this is a bug or intended behavior.

In React Query versions up to 5.66.3, only the dataUpdatedAt field was compared during hydration, which prevented this issue from occurring. However, starting with version 5.66.4, the dehydrated state now also includes pending queries because the shouldDehydrateQuery setting returns true even when query.state.status is 'pending'. This means that queries that have not yet completed are being included in the dehydrated state.

During hydration, React Query internally attaches .then() and .catch() chains to these pending queries, effectively generating new Promise objects—especially when error redaction logic is applied. As a result, each render creates a new Promise instance, and the promise.status comparison logic interprets this as the query being “new” or updated. This triggers re-hydration, which in turn causes another render, leading to an infinite render loop.

The problem is exacerbated in a development environment, particularly with Next.js' Hot Module Replacement (HMR) or Fast Refresh. When code changes while a query is still pending, the old pending Promise conflicts with the new code. This clash makes the infinite rendering issue much more likely to occur. In production builds, where a full page reload happens, this issue tends not to manifest as prominently.

To reproduce the issue, use React Query version 5.66.4 or later with a configuration that includes pending queries in the dehydrated state (i.e., shouldDehydrateQuery returns true when query.state.status is 'pending'). Then, introduce an artificial delay in the getApiV1ActivitiesOptions function—for example, by adding await new Promise(r => setTimeout(r, 5000))

This delay causes the query to remain pending. Next, while the query is still pending, change the code via HMR—for instance, by uncommenting and then commenting out the delay code. At this point, because the server-side (or RSC) prefetchQuery has run dehydrate while the query was still pending, the client’s HydrationBoundary receives a dehydrated state that includes the old pending Promise. The new code then interprets the Promise as “new” on every render, triggering an infinite render loop.

@juliusmarminge
Copy link
Collaborator

still works fine ?

Image

CleanShot.2025-02-23.at.10.13.13.mp4

@joseph0926
Copy link
Contributor

joseph0926 commented Feb 23, 2025

I don't know if this is the intended behavior of “@tanstack/react-query”, but if you want to reproduce the above issue, you can do it like this (It is quite an edge case and will probably only be reproduced in HMR development environments.)

Image

@juliusmarminge
Copy link
Collaborator

oh you're changing the queryFn... that's annoying to test xd

@joseph0926
Copy link
Contributor

Yeah, the idea behind the reproduction was to intentionally reproduce the “promise” changing while “pending”.
I don't know if this will happen in the real world,,

@Ephem
Copy link
Collaborator

Ephem commented Mar 3, 2025

@juliusmarminge This is happening with failed queries for me. What I think is happening is that:

  • The HydrationBoundary code is expected to be idempotent, if it rerenders with the same state it shouldn't hydrate anything
  • The change introduced this: dehydratedQuery.promise.status !== existingQuery.promise.status, but since promise.status can change from pending to failed without the state changing, we end up in an infinite loop where the query keeps being re-added to the hydrationQueue, changing the status, triggering being re-added etc.

@Ephem Ephem closed this as completed Mar 3, 2025
@Ephem Ephem reopened this Mar 3, 2025
@Ephem
Copy link
Collaborator

Ephem commented Mar 3, 2025

Closing was accidental.

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

No branches or pull requests

6 participants