feat: useFetch() always returns a stable promise with .resolved property#3752
feat: useFetch() always returns a stable promise with .resolved property#3752
Conversation
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 detectedLatest commit: 59019d3 The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
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 |
|
Size Change: 0 B Total Size: 80.6 kB ℹ️ View Unchanged
|
There was a problem hiding this comment.
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 Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Motivation
useFetch()previously returnedPromise | undefined, returningundefinedwhen cached data was valid. This made it impossible to pass directly toReact.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.resolvedboolean property:resolved: false, flipped totrueon settleresolved: trueundefined(disabled state)The same promise reference is preserved across re-renders via
useRef, so after a fetch completes andexpiresAtchanges, the memo re-runs but keeps the existing (now-resolved) promise for the same key.Code size is minimized with a module-level
RESOLVEDsingleton and atrackPromisehelper 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 implementationpackages/react/src/hooks/useFetch.native.ts— React Native implementationpackages/vue/src/consumers/useFetch.ts— Vue implementationdocs/core/api/useFetch.md— updated behavior table, types, and exampleswebsite/blog/2026-01-19-v0.16-release-announcement.md— release notesOpen 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 onundefined/truthiness will need updates.Overview
useFetch()in@data-client/reactand@data-client/vuenow always returns a stable promise (for non-nullargs), adding a booleanpromise.resolvedto indicate in-flight vs completed, instead of returningundefinedwhen data is already fresh.React (web + native) now keeps a per-key promise in a
useRef, uses a sharedRESOLVEDsingleton for fresh data, and wraps real fetches withtrackPromise()to flip.resolvedon settle; Vue mirrors this behavior with a trackedlastPromise. Tests and docs/release notes are updated to cover the new contract (including Reactuse(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.