Fix: Do Not Pass Execute Directly to Watch in Nuxt

Error message:
Do not pass `execute` directly to watch - use `refresh` or explicitly call the function.
data fetching 2025-01-25

What Causes This Warning?

This warning appears when you pass the execute function from useFetch or useAsyncData directly to Vue’s watch(). This creates issues because execute can trigger multiple unwanted fetches.

The Problem

<script setup>
const { data, execute } = useFetch('/api/users')

// ❌ Wrong - passing execute directly to watch
watch(someRef, execute)

// ❌ Wrong - also incorrect
watch(() => userId.value, execute)
</script>

The Fix

Use refresh Instead

<script setup>
const { data, refresh } = useFetch('/api/users')

// ✅ Correct - use refresh
watch(someRef, () => {
  refresh()
})
</script>

Or Call Function Explicitly

<script setup>
const { data, execute } = useFetch('/api/users', { immediate: false })

// ✅ Correct - wrap in a function
watch(someRef, () => {
  execute()
})
</script>

Understanding execute vs refresh

MethodPurposeWhen to Use
execute()Run the fetch (respects immediate option)Initial fetch, manual triggers
refresh()Re-run the fetch (ignores immediate)Reactively update data
<script setup>
const { data, execute, refresh, pending } = useFetch('/api/data', {
  immediate: false  // Don't fetch on mount
})

// Initial fetch
onMounted(() => {
  execute()  // Runs the fetch
})

// Reactive updates
watch(filters, () => {
  refresh()  // Re-fetches when filters change
})
</script>

Common Patterns

Watch with Computed URL

<script setup>
const userId = ref(1)

// ✅ Best approach - let useFetch handle reactivity
const { data, refresh } = useFetch(() => `/api/users/${userId.value}`)

// URL changes automatically trigger refetch
// No watch needed!
</script>

Manual Refresh on Dependency Change

<script setup>
const category = ref('electronics')
const sortBy = ref('price')

const { data, refresh } = useFetch('/api/products', {
  query: {
    category: category,
    sort: sortBy
  }
})

// ✅ Use refresh if you need manual control
watch([category, sortBy], () => {
  refresh()
})
</script>

With useAsyncData

<script setup>
const page = ref(1)

const { data, refresh } = await useAsyncData(
  'products',
  () => $fetch('/api/products', { query: { page: page.value } })
)

// ✅ Correct - use refresh in watch
watch(page, () => {
  refresh()
})

// Or use the key for automatic refresh
const { data: autoData } = await useAsyncData(
  () => `products-${page.value}`,  // Key changes, triggers refetch
  () => $fetch('/api/products', { query: { page: page.value } })
)
</script>

Debounced Refresh

<script setup>
import { useDebounceFn } from '@vueuse/core'

const searchQuery = ref('')
const { data, refresh } = useFetch(() => `/api/search?q=${searchQuery.value}`)

// ✅ Debounced refresh
const debouncedRefresh = useDebounceFn(() => {
  refresh()
}, 300)

watch(searchQuery, () => {
  debouncedRefresh()
})
</script>

Conditional Refresh

<script setup>
const userId = ref(null)
const { data, refresh } = useFetch(() => `/api/users/${userId.value}`, {
  immediate: false  // Don't fetch until we have a userId
})

// ✅ Only refresh when userId is valid
watch(userId, (newId) => {
  if (newId) {
    refresh()
  }
})
</script>

Alternative: watch Option

Use the built-in watch option instead of manual watching:

<script setup>
const category = ref('all')

// ✅ Built-in watch option
const { data } = useFetch('/api/products', {
  query: { category },
  watch: [category]  // Automatically refreshes when category changes
})
</script>
<script setup>
const filters = reactive({
  category: 'all',
  minPrice: 0,
  maxPrice: 1000
})

// ✅ Watch multiple values
const { data } = useFetch('/api/products', {
  query: filters,
  watch: [() => filters.category, () => filters.minPrice]
})
</script>

Deep Watch

<script setup>
const filters = ref({ nested: { value: 1 } })

// ✅ Deep watch with refresh
watch(
  filters,
  () => { refresh() },
  { deep: true }
)

// Or use the watch option with deep
const { data } = useFetch('/api/data', {
  query: filters,
  watch: [filters],
  deep: true
})
</script>

With Transform

<script setup>
const type = ref('active')

const { data, refresh } = useFetch('/api/items', {
  query: { type },
  transform: (items) => items.filter(i => i.visible)
})

// ✅ Transform runs again on refresh
watch(type, () => {
  refresh()
})
</script>

Quick Checklist

  • Don’t pass execute directly to watch()
  • Use refresh() for reactive updates
  • Wrap execute() in a function if needed
  • Consider using the built-in watch option
  • Use computed URLs for automatic reactivity
  • Add debouncing for frequently changing values