Skip to content

feat: useFetch() always returns a stable promise with .resolved property#3752

Merged
ntucker merged 6 commits intomasterfrom
usefetch-stable-promise
Mar 1, 2026
Merged

feat: useFetch() always returns a stable promise with .resolved property#3752
ntucker merged 6 commits intomasterfrom
usefetch-stable-promise

Conversation

@ntucker
Copy link
Collaborator

@ntucker ntucker commented Mar 1, 2026

Motivation

useFetch() previously returned Promise | undefined, returning undefined when cached data was valid. This made it impossible to pass directly to React.use() (which requires a thenable), and meant checking fetch status relied on truthiness rather than a semantic property.

Solution

useFetch() now always returns a stable promise reference (for non-null args) with a .resolved boolean property:

  • Fetching: returns the fetch promise with resolved: false, flipped to true on settle
  • Valid data: returns a pre-resolved singleton promise with resolved: true
  • Null args: still returns undefined (disabled state)

The same promise reference is preserved across re-renders via useRef, so after a fetch completes and expiresAt changes, the memo re-runs but keeps the existing (now-resolved) promise for the same key.

Code size is minimized with a module-level RESOLVED singleton and a trackPromise helper that uses a single shared callback for .then(r, r).

Packages affected: @data-client/react (minor), @data-client/vue (minor)

Files changed:

  • packages/react/src/hooks/useFetch.ts — web implementation
  • packages/react/src/hooks/useFetch.native.ts — React Native implementation
  • packages/vue/src/consumers/useFetch.ts — Vue implementation
  • Tests for all three
  • docs/core/api/useFetch.md — updated behavior table, types, and examples
  • website/blog/2026-01-19-v0.16-release-announcement.md — release notes

Open questions

N/A

Made with Cursor


Note

High Risk
This is a breaking API change to useFetch() return semantics and TypeScript types in both React and Vue packages, which can affect application control-flow and loading-state logic. Risk is mitigated by added cross-platform tests, but downstream code that relied on undefined/truthiness will need updates.

Overview
useFetch() in @data-client/react and @data-client/vue now always returns a stable promise (for non-null args), adding a boolean promise.resolved to indicate in-flight vs completed, instead of returning undefined when data is already fresh.

React (web + native) now keeps a per-key promise in a useRef, uses a shared RESOLVED singleton for fresh data, and wraps real fetches with trackPromise() to flip .resolved on settle; Vue mirrors this behavior with a tracked lastPromise. Tests and docs/release notes are updated to cover the new contract (including React use(promise) usage) and a changeset marks both packages as major.

Written by Cursor Bugbot for commit 59019d3. This will update automatically on new commits. Configure here.

useFetch() now always returns a promise (for non-null args), even when
data is already cached. The same promise reference is preserved across
re-renders. A `.resolved` boolean property indicates fetch status,
replacing truthiness checks. This enables direct usage with React.use()
and useLoading().

Made-with: Cursor
@changeset-bot
Copy link

changeset-bot bot commented Mar 1, 2026

🦋 Changeset detected

Latest commit: 59019d3

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

This PR includes changesets to release 6 packages
Name Type
@data-client/react Major
@data-client/vue Major
@data-client/img Major
@data-client/test Major
test-bundlesize Patch
coinbase-lite 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

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

Size Change: 0 B

Total Size: 80.6 kB

ℹ️ View Unchanged
Filename Size
examples/test-bundlesize/dist/App.js 3.42 kB
examples/test-bundlesize/dist/polyfill.js 307 B
examples/test-bundlesize/dist/rdcClient.js 10.2 kB
examples/test-bundlesize/dist/rdcEndpoint.js 6.24 kB
examples/test-bundlesize/dist/react.js 59.7 kB
examples/test-bundlesize/dist/webpack-runtime.js 726 B

compressed-size-action

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Free Tier Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

@codecov
Copy link

codecov bot commented Mar 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.04%. Comparing base (57d53d8) to head (59019d3).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3752      +/-   ##
==========================================
+ Coverage   98.02%   98.04%   +0.02%     
==========================================
  Files         150      151       +1     
  Lines        2787     2818      +31     
  Branches      549      552       +3     
==========================================
+ Hits         2732     2763      +31     
  Misses         11       11              
  Partials       44       44              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ntucker ntucker merged commit 3c3bfe8 into master Mar 1, 2026
24 checks passed
@ntucker ntucker deleted the usefetch-stable-promise branch March 1, 2026 15:32
@github-actions github-actions bot mentioned this pull request Mar 1, 2026
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.

1 participant