How to use computed properties with tanstack's vue-query?

1.4k views Asked by At

I'm used to using Pinia/Vuex for dispatching axios requests and storing the data in the store, then using the getters for computational operations on the data. I'm using Vue 3.

A simple example: let's say I have a table, I fetch the data from the action and put it in the state, then use different getters for different sorting filters

Now, vue-query docs say that there might be no need for Vuex/Pinia/state management when using vue-query. This is where my problem comes in, how to organise the code so I can still use the computed properties/getters without having them locally in the file where I'm using vue-query?

I'm aware I might be totally missing the point of vue-query. In that case, please list some resources where I can learn more about it (that's not just the docs)..

I tried fetching the data from the store (using the action in the store as the query function), then relying on vue-query properties isLoading, isError in the template. After that, I used the getters from the store.

This feels wrong, duplicated data in vue-query and Pinia??

Here's the minimal example:

<script setup lang="ts">
const { isLoading, isError, error } = useQuery({
  queryKey: ["items"],
  queryFn: store.fetchItems,
});

const {
  listItems: items,
} = storeToRefs(store);
</script>

<template>
    <template v-if="isLoading"> loading </template>
    <template v-else-if="isError">
      {{ error }}
    </template>
    <template v-else>
      <div>{{ items }}</div>
    </template>
</template>

store has:

  const list: Ref<Item[]> = ref([]);
  const listItems = computed(() => list.value);

  async function fetchItems(): Promise<Item[]> {
    try {
      const response = await axios.get<Item[]>(`${dbUrl}/items`);

      list.value = response.data || [];
      return list.value;
    } catch (err) {
      list.value = [];
      throw new Error(err as string);
    }
  }

  return {
    fetchItems,
    listItems,
  }
1

There are 1 answers

0
Rogier Pennink On

It is indeed true that you can replace the store with a query if you're only using it to cache data for use across multiple components. However, I get the feeling that you're not using the store for the right reasons:

This is where my problem comes in, how to organise the code so I can still use the computed properties/getters without having them locally in the file where I'm using vue-query?"

The store is not necessarily there for code reuse (although code reuse can be a consequence of using it). It is there to deal with the problem of accessing application state from multiple different components.

In this particular example, assuming you want to stick with vue-query, the store is not needed at all. You don't even need the computed(() => list.value), since vue-query basically gives you the result as a ref:

const { data: items, isError, error, isLoading } = useQuery({
  queryKey: ["items"],
  queryFn: async () => {
    const response = await axios.get<Item[]>(`${dbUrl}/items`);
    return response.data;
  },
  // This is basically your "fallback value" in case of errors or the
  // data not being present yet
  initialData: []
});

Now, it can indeed get a bit tedious to write out this code in multiple places (which is partially what you seem to want to prevent with the use of the store), so for that I'd recommend just turning it into a re-usable composable:

const useListItems = () => {
  const { data: items, isError, error, isLoading } = useQuery({
    queryKey: ["items"],
    queryFn: async () => {
      const response = await axios.get<Item[]>(`${dbUrl}/items`);
      return response.data;
    },
    initialData: []
  });

  return { items, isError, error, isLoading };
}

Now in your component(s) you can just use it as:

import { useListItems } from '@/composables/useListItems';

const { items, isError, error, isLoading } = useListItems();