Skip to content

React 19 Fixes #688

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

Merged
merged 4 commits into from
Aug 7, 2025
Merged

React 19 Fixes #688

merged 4 commits into from
Aug 7, 2025

Conversation

stevensJourney
Copy link
Collaborator

Overview

Closes #687

I have only been able to reproduce the issue above when using React 19 in Strict mode.

It seems like the flow is as follows.

  • Initially useWatchedQuerySuspenseSubscription is called. We create a temporary hold and suspend the component during loading.
  • After loading is complete React calls useWatchedQuerySuspenseSubscription which calls useTemporaryHold - since the query is not loading, we don't need to create a temporary hold. We return a no-op releaseHold method (we don't assign it to the releaseTemporaryHold Ref since it's a no-op).
  • A third invocation of useWatchedQuerySuspenseSubscription is eventually triggered, we get the value Ref value of releaseTemporaryHold (which has not been assigned). We then enter the useEffect in useWatchedQuerySuspenseSubscription and attempt to release using the undefined method.

Generally the current implementation is unnecessarily flawed. We cannot feasibly remove the temporary hold from useWatchedQuerySuspenseSubscription. We have to rely on the polling to remove this.

There are primarily 2 scenarios

If the WatchedQuery is loading on the first render:

During the initial render (while the query is loading). We will detect isLoading == true and throw the pending promise which triggers suspense. Throwing this promise will not trigger the useEffect in this "render".

Once the promise resolves React's suspense will reinvoke useWatchedQuerySuspenseSubscription (without any previous state). Calling useTemporaryHold will not create a temporary hold since the WatchedQuery is no longer in the loading state. The useEffect will run, but releasing would be a no-op.

If the supplied WatchedQuery was already loaded on the first render:

We will not suspend or create any temporary hold. There is nothing to release in the useWatchedQuerySuspenseSubscription::useEffect.

I'm not entirely sure why React 19 Strict mode triggered the third invocation of useWatchedQuerySuspenseSubscription where the useEffect was triggered, but removing this unnecessary check avoids this issue entirely.

The should suspend on initial load test ensures that temporary holds are removed correctly.

I manually verified that the unit tests pass when using React 19. This required manually extracting the unit tests from the Monorepo. We should eventually update React in this monorepo.

Copy link

changeset-bot bot commented Aug 7, 2025

🦋 Changeset detected

Latest commit: 04d35ce

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@powersync/react Patch
@powersync/react-native Patch
@powersync/tanstack-react-query Patch
@powersync/diagnostics-app Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@stevensJourney stevensJourney marked this pull request as ready for review August 7, 2025 13:35
@stevensJourney stevensJourney merged commit dce523a into main Aug 7, 2025
9 checks passed
@stevensJourney stevensJourney deleted the fix-suspending-query branch August 7, 2025 15:04
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

Successfully merging this pull request may close these issues.

releaseHold is not a function (it is undefined)
2 participants