From 23b1cc7f7b7be7c2355bb5e53ae67487aeb6967d Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 8 Jun 2026 14:35:58 -0600 Subject: [PATCH 1/2] fix(electric): bound refresh wait for on-demand subsets --- .changeset/tame-rn-live-poll-refresh.md | 5 +++++ .../electric-db-collection/src/electric.ts | 22 ++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 .changeset/tame-rn-live-poll-refresh.md diff --git a/.changeset/tame-rn-live-poll-refresh.md b/.changeset/tame-rn-live-poll-refresh.md new file mode 100644 index 000000000..e65a11816 --- /dev/null +++ b/.changeset/tame-rn-live-poll-refresh.md @@ -0,0 +1,5 @@ +--- +"@tanstack/electric-db-collection": patch +--- + +Bound the wait for Electric stream refreshes before loading on-demand subsets so native fetch implementations that do not promptly abort long polls do not keep live queries loading until the poll times out. diff --git a/packages/electric-db-collection/src/electric.ts b/packages/electric-db-collection/src/electric.ts index 72a9a072e..c7d36c8c4 100644 --- a/packages/electric-db-collection/src/electric.ts +++ b/packages/electric-db-collection/src/electric.ts @@ -64,6 +64,8 @@ export { isChangeMessage, isControlMessage } from '@electric-sql/client' const debug = DebugModule.debug(`ts/db:electric`) +const FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS = 250 + /** * Symbol for internal test hooks (hidden from public API) */ @@ -481,11 +483,23 @@ function createLoadSubsetDedupe>({ // When the stream is already up-to-date, it may be in a long-poll wait. // Forcing a disconnect-and-refresh ensures requestSnapshot gets a response // from a fresh server round-trip rather than waiting for the current poll to end. - // If the refresh fails (e.g., PauseLock held during subscriber processing in - // join pipelines), we fall through to requestSnapshot which still works. + // Some native fetch implementations (notably React Native/Expo) may not abort + // long-poll requests promptly. Bound the wait so on-demand live queries don't + // remain loading until the long-poll naturally times out. + // If the refresh fails or times out, we fall through to requestSnapshot which + // still works. if (stream.isUpToDate) { + let timeoutId: ReturnType | undefined try { - await stream.forceDisconnectAndRefresh() + await Promise.race([ + stream.forceDisconnectAndRefresh(), + new Promise((resolve) => { + timeoutId = setTimeout( + resolve, + FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS, + ) + }), + ]) } catch (error) { if (handleSnapshotError(error, `forceDisconnectAndRefresh`)) { return @@ -494,6 +508,8 @@ function createLoadSubsetDedupe>({ `${logPrefix}forceDisconnectAndRefresh failed, proceeding to requestSnapshot: %o`, error, ) + } finally { + clearTimeout(timeoutId) } } From 56f2bf3cdb988e6a261783e929f7698ef89f4787 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:37:16 +0000 Subject: [PATCH 2/2] ci: apply automated fixes --- .changeset/tame-rn-live-poll-refresh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tame-rn-live-poll-refresh.md b/.changeset/tame-rn-live-poll-refresh.md index e65a11816..073897f82 100644 --- a/.changeset/tame-rn-live-poll-refresh.md +++ b/.changeset/tame-rn-live-poll-refresh.md @@ -1,5 +1,5 @@ --- -"@tanstack/electric-db-collection": patch +'@tanstack/electric-db-collection': patch --- Bound the wait for Electric stream refreshes before loading on-demand subsets so native fetch implementations that do not promptly abort long polls do not keep live queries loading until the poll times out.