According to the doc, useFetch.data should be a ref but it's a computed?

const { data: posts } = await useFetch<{ results: object[] }>(
'https://jsonplaceholder.typicode.com/posts'
)
const { data: posts } = await useFetch<{ results: object[] }>(
'https://jsonplaceholder.typicode.com/posts'
)
What I am missing?
No description
No description
10 Replies
kapa.ai
kapa.ai3mo ago
Hi @Romain 'Maz' B.. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Short answer: In Nuxt, the data returned by useFetch is a Vue ref. It may be implemented internally as a computed ref for features like shared/cached state and reactive invalidation, but its public type and usage are Ref (accessed with .value in script). The API docs specify data: Ref<DataT | undefined> as the return type, alongside error and status also being refs, while refresh/execute/clear are plain functions (useFetch – Return Values, useFetch – Type). So, even if devtools show it as “computed,” you should treat it as a Ref in userland:
const { data: posts } = await useFetch<{ results: object[] }>('<https://jsonplaceholder.typicode.com/posts'>)
// In <script setup>, access with .value:
console.log(posts.value)
const { data: posts } = await useFetch<{ results: object[] }>('<https://jsonplaceholder.typicode.com/posts'>)
// In <script setup>, access with .value:
console.log(posts.value)
(Getting started: useFetch, API: useFetch, API: useFetch type) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@601146927005827074> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Romain 'Maz' B.
Romain 'Maz' B.OP3mo ago
The fact is that I need to force the data to be a ref by re-assigning it like this to be fully reactive:
const { data } = await useFetch<{ results: object[] }>(
'https://jsonplaceholder.typicode.com/posts'
)
const posts = ref(data)
const { data } = await useFetch<{ results: object[] }>(
'https://jsonplaceholder.typicode.com/posts'
)
const posts = ref(data)
Is data a shallowRef @kapa.ai
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Yes. In Nuxt 4, data returned from useFetch/useAsyncData is a shallowRef by default. This means updates that replace the whole value trigger reactivity, but mutating nested properties does not. You can opt into deep reactivity per call with { deep: true } if needed. Example:
const { data } = await useFetch('/api/posts', { deep: true })
const { data } = await useFetch('/api/posts', { deep: true })
(Shallow Data Reactivity in useAsyncData and useFetch, useFetch – Return Values) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@601146927005827074> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Romain 'Maz' B.
Romain 'Maz' B.OP3mo ago
Hmm, the deep option doesn't change anything @kapa.ai
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Two key points that can explain why setting deep: true “doesn’t change anything”: - shallowRef vs deep: In Nuxt 4, data from useFetch/useAsyncData is shallow by default for performance. deep: true switches the ref itself to deep reactivity (so nested mutations can trigger updates), but this only affects how Vue tracks changes to data, not how data is shared/cached or when it refetches (Shallow Data Reactivity). If you’re mutating nested fields in place and still not seeing updates, ensure you actually passed deep: true to useFetch (or set it globally via experimental.defaults.useAsyncData.deep) and that you’re mutating the same data instance returned by useFetch (not a copy) (same source). - Shared state and consistency: useFetch shares refs for the same key/URL+options across components. If any other call with the same key/URL used different deep/transform/pick/default/getCachedData options, Nuxt enforces option consistency and may warn, and behavior might not match your expectations. Ensure all calls with the same key/URL use consistent options or use a distinct key to isolate instances (Singleton Data Fetching Layer, Minimize payload size → Shared State and Option Consistency). Quick checks: - Use deep per call: const { data } = await useFetch('/api/posts', { deep: true }) (same “Shallow Data Reactivity” doc). - If you need isolation, provide a unique key: useFetch('/api/posts', { key: 'posts-deep', deep: true }) to avoid shared shallow instances (Reactive Keys and Shared State). - Don’t wrap the ref in ref again (ref(data)); use data directly and mutate nested properties only when deep: true. Otherwise, replace the whole value to trigger updates with shallow refs (e.g., data.value = { ...data.value, nested: { ... } }) (Shallow Data Reactivity). If after setting deep: true it still behaves shallow, verify no other useFetch call with the same key/URL is providing conflicting options; if needed, give this call its own key to ensure the deep option applies to the instance you’re using (same sources above). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@601146927005827074> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Romain 'Maz' B.
Romain 'Maz' B.OP3mo ago
Ok... The issue was not the useFetch deep true option but the nuxt-sanctum-auth module that I use in the project which override some of the native useFetch behavior including the deep true option...
Cake
Cake3mo ago
btw, posts here won't be reactive

Did you find this page helpful?