Repository navigation
fix(nuxt): update useFetch key even with watch: false - #35002
Conversation
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/nuxt/use-fetch.test.ts`:
- Around line 372-390: Two effectScope instances (scope1 and scope2 created via
effectScope() and run(createScope())) are not stopped, risking cross-test
leakage; fix by calling scope1.stop() and scope2.stop() after the test actions
(after the awaits and assertions) to dispose watchers/deps created by
createScope(); ensure you add the stop calls referencing the exact variables
scope1 and scope2 so both scopes are cleaned up regardless of assertion outcomes
(or wrap in finally if you prefer deterministic teardown).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 00b8dc6c-6e56-4ca9-8ea2-10b9bc232a37
📒 Files selected for processing (3)
packages/nuxt/src/app/composables/asyncData.tspackages/nuxt/src/app/composables/fetch.tstest/nuxt/use-fetch.test.ts
| const scope1 = effectScope() | ||
| const { data: d1, id: i1, query: q1, execute: e1 } = scope1.run(createScope())! | ||
| const scope2 = effectScope() | ||
| const { data: d2 } = scope2.run(createScope())! | ||
|
|
||
| expect.soft(d1.value).toStrictEqual(undefined) | ||
| expect.soft(d2.value).toStrictEqual(undefined) | ||
|
|
||
| q1.value.someInput = 'test' | ||
| i1.value++ | ||
| await e1() | ||
| await flushPromises() | ||
| await nextTick() | ||
| await flushPromises() | ||
|
|
||
| // d1 should have data; d2 should remain untouched | ||
| expect.soft(d1.value).toMatchObject({ url: '/api/key/2' }) | ||
| expect.soft(d2.value).toStrictEqual(undefined) | ||
| }) |
There was a problem hiding this comment.
Stop created effect scopes to avoid cross-test leakage.
At Line 372 and Line 374, two effectScope() instances are created but never disposed. This can leak watchers/deps into subsequent tests and make outcomes order-dependent.
Proposed fix
const scope1 = effectScope()
const { data: d1, id: i1, query: q1, execute: e1 } = scope1.run(createScope())!
const scope2 = effectScope()
const { data: d2 } = scope2.run(createScope())!
- expect.soft(d1.value).toStrictEqual(undefined)
- expect.soft(d2.value).toStrictEqual(undefined)
+ try {
+ expect.soft(d1.value).toStrictEqual(undefined)
+ expect.soft(d2.value).toStrictEqual(undefined)
- q1.value.someInput = 'test'
- i1.value++
- await e1()
- await flushPromises()
- await nextTick()
- await flushPromises()
+ q1.value.someInput = 'test'
+ i1.value++
+ await e1()
+ await flushPromises()
+ await nextTick()
+ await flushPromises()
- // d1 should have data; d2 should remain untouched
- expect.soft(d1.value).toMatchObject({ url: '/api/key/2' })
- expect.soft(d2.value).toStrictEqual(undefined)
+ // d1 should have data; d2 should remain untouched
+ expect.soft(d1.value).toMatchObject({ url: '/api/key/2' })
+ expect.soft(d2.value).toStrictEqual(undefined)
+ } finally {
+ scope1.stop()
+ scope2.stop()
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const scope1 = effectScope() | |
| const { data: d1, id: i1, query: q1, execute: e1 } = scope1.run(createScope())! | |
| const scope2 = effectScope() | |
| const { data: d2 } = scope2.run(createScope())! | |
| expect.soft(d1.value).toStrictEqual(undefined) | |
| expect.soft(d2.value).toStrictEqual(undefined) | |
| q1.value.someInput = 'test' | |
| i1.value++ | |
| await e1() | |
| await flushPromises() | |
| await nextTick() | |
| await flushPromises() | |
| // d1 should have data; d2 should remain untouched | |
| expect.soft(d1.value).toMatchObject({ url: '/api/key/2' }) | |
| expect.soft(d2.value).toStrictEqual(undefined) | |
| }) | |
| const scope1 = effectScope() | |
| const { data: d1, id: i1, query: q1, execute: e1 } = scope1.run(createScope())! | |
| const scope2 = effectScope() | |
| const { data: d2 } = scope2.run(createScope())! | |
| try { | |
| expect.soft(d1.value).toStrictEqual(undefined) | |
| expect.soft(d2.value).toStrictEqual(undefined) | |
| q1.value.someInput = 'test' | |
| i1.value++ | |
| await e1() | |
| await flushPromises() | |
| await nextTick() | |
| await flushPromises() | |
| // d1 should have data; d2 should remain untouched | |
| expect.soft(d1.value).toMatchObject({ url: '/api/key/2' }) | |
| expect.soft(d2.value).toStrictEqual(undefined) | |
| } finally { | |
| scope1.stop() | |
| scope2.stop() | |
| } | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@test/nuxt/use-fetch.test.ts` around lines 372 - 390, Two effectScope
instances (scope1 and scope2 created via effectScope() and run(createScope()))
are not stopped, risking cross-test leakage; fix by calling scope1.stop() and
scope2.stop() after the test actions (after the awaits and assertions) to
dispose watchers/deps created by createScope(); ensure you add the stop calls
referencing the exact variables scope1 and scope2 so both scopes are cleaned up
regardless of assertion outcomes (or wrap in finally if you prefer deterministic
teardown).
@nuxt/kit
@nuxt/nitro-server
nuxt
@nuxt/rspack-builder
@nuxt/schema
@nuxt/vite-builder
@nuxt/webpack-builder
commit: |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
Why cant this be an "official" option for async data (AsyncDataOptions)?
There was a problem hiding this comment.
you mean, watch: false?
There was a problem hiding this comment.
if you think it's a good idea, let's open a new PR to add the feature, but we can still get this one in as a fix for the next patch

🔗 Linked issue
resolves #32102
resolves #32437
📚 Description
with
watch: false,useFetchwas unwrapping the key to a plain string before passing it touseAsyncData, which froze the 'key'that's what's behind both linked issues:
getCachedDataalways saw the original key, and twouseFetchcalls that started out with the same shape kept sharing one async data slot even after their inputs diverged - soexecute()on one overwrote the other'sdata.this came from #32019, which fixed
watch: falsenot preventing auto-refetch (#32016) but disabled key reactivity entirely.so this pr passes the reactive key through, and adds an internal
_keyTriggersExecuteflag onuseAsyncData's key watcher. when false, the data slot is still migrated to the new key (sogetCachedDataand manualexecute()see it), but the auto-execute on key change is skipped.useFetchsets it to false wheneverwatch: false.reactive query/params still don't trigger auto-refetch.