watchEffect with side effect but without infinite loop

Viewed 1364

what I am trying to do is:

  1. construct an URL based on props
  2. initially and whenever the URL changes, fetch some data

Since this is asynchronous and I also want to indicate loading, I use this construct:

const pageUrl = computed(() => `/api/${props.foo}/${props.bar}`)

const state = reactive({
    page: null,
    error: null,
    loading: false
})

watchEffect(async () => {
    state.loading = true
    try {
      const resp = await axios.get(pageUrl.value)
      state.page = resp.data
    } catch (err) {
      state.error = err
      console.log(err)
    }

    state.loading = false
})

// return page, loading, error for the component to use

The problem is that this seems to run in an infinite loop because in the body, I am not only reacting to the pageUrl, but also to state which itself is modified in the function body.

Alternatively, I can use watch(pageUrl, async pageUrl => { ... }), but this seems only to be triggered when pageUrl changes (in my case: I modify the URL because the props are updated via vue-router, but not when I initially visit the URL).

What should I do here, is my idea of signalling the loading state not appropriate here?

From a logical point of view, the page is a computed value, the only reason I use watch here is that it's asynchronous and might yield an error as well.

1 Answers

Thanks to Husam I got aware of the bug, and it seems like changing a piece of state twice introduces this behaviour - in my case setting loading to true and then again to false.

This behaviour is not apparent in Vue3, and a workaround (and in general maybe the much cleaner method) could be to directly use watch instead of watchEffect.

The source code shows different overloads of the funciton, and there is an options argument that is not directly documented in the Vue3 API. There, I found that my call above needs to read like watch(value, async value => { effect }, { immediate: true }).

Related