Vue-apollo: @vue/apollo-composable - useQuery outside setup

Created on 28 Jul 2020  ·  14Comments  ·  Source: vuejs/vue-apollo

Is your feature request related to a problem? Please describe.
In our project we're using composition api to manage state. Some components require global state, based on state we set we execute a query.

Something among these lines:

useOrders.ts

const state = reactive<OrderState>({
   orderLines: [],
   orderId: undefined
})

const { onResult } = useQuery(gql`...`, ..., () => ({ enabled: state.orderId !== undefined }))

onResult((data) => {
   state.orderLines = data;
})

The above is not working because the query has a dependency on getCurrentInstance() to determine if the query should run serverside/clientside.

Describe the solution you'd like
A simple solution without much side effects is to check if vm !== null

const vm = getCurrentInstance()
const isServer = vm !== null && vm.$isServer

Describe alternatives you've considered
Currently we check in our useOrders hook if the query has been initialized. But this is not clean in our opinion, it would be better if there is no hard dependency on getCurrentInstance().

enhancement v4

Most helpful comment

@Akryum
This is also an issue when calling an async function that is in turn calling useQuery, even if the call chain started in setup as the current VM might be set to null by the time useQuery gets called again.

All 14 comments

This is also an issue for trying to run mutations/queries within Router.beforeEach.

Same issue here, tried using useQuery outside of a component and got an error, seems like a common use case (was my first attempt of using useQuery)

Because getCurrentInstance() is only avaliable in setup and lifecycle hooks. https://github.com/vuejs/composition-api/issues/455
I think we should abandon the getCurrentInstance。

Is there a workaround for this? Is it possible to have useQuery or useMutation inside of its own composition function eg. useNotifications.ts To me it doesn't make sense to have these functions only useable within a setup() function since there is often logic surrounding these queries that needs to be reused across components.

@seanaye you can use queries and mutations in useFuctions, as they're called within the setup function.

I receive the same error when I try to fetch data after button click.
I have the following code in my setup()

<q-btn label="Update" color="primary" @click="doSelect" class="q-mb-md"/>
         function doSelect() {
                const {result: seriesResult} = useQuery(getDerivativeInstrument)
            }

Can can I fetch data at all if useQuery() doesn't work ?

How to run queries and mutations before Vue instance created (Quasar Boot files)?

I've tried to achieve this with apollo-composable, but there is no root while calling useMutation. So now best way for me is to use original 'vue-apollo', register client first and then call query/mutate directly on apolloProvider.defaultClient instance. It works as expected and has more control over process.

Maybe we need to modify library to provide 'client' option to useQuery/useMutation for that case? Something like

useMutation(SignInDocument, {
  client: app.apolloProvider.defaultClient
})

So it must use our client instead of looking for root object.

But the instance is created. I registered DefaultApolloClient and it works from setup()

According https://github.com/vuejs/composition-api/issues/455#issuecomment-667702942 it is a normal that getCurrentInstance() is not available in functions.

In my scenario, user should select an option and click the button to run query. But useQuery() doesn't work in functions.

Maybe this issue can be solved in another way, this is my third day with quasar and ts, so maybe I miss the obvious ? I've found refetch, but this is suitable if I want to refresh the data, not initially fetch it.

hi,
I have the same issue. I cannot execute useMutation outside setup(). I am doing an authentication application using JWT with a refresh-token. When the user enter its credentials and the server return the token a timer is started and run a function with the refresh token login.

@Akryum
This is also an issue when calling an async function that is in turn calling useQuery, even if the call chain started in setup as the current VM might be set to null by the time useQuery gets called again.

@seanaye - _Is it possible to have useQuery or useMutation inside of its own composition function_

Yes.

Your composable function outside the setup - maybe located at compossbles directory

import { reactive, toRefs } from "vue"; 
import { useQuery, useResult } from "@vue/apollo-composable";

export default function useSomething() {
  const state = reactive({
   my_thing: null,
  })

  // Encapsulate the query inside an async function that you will return
  const fetchSomething = async () => {
    const { result, loading, error } = useQuery(gql`...`, null, {
      fetchPolicy: "network-only" // check for other available options
    });

    // Set up your state
    state.my_thing = useResult(result, {}, data => data.my_thing); // 'data.my_thing' can be whatever the node is named in your api gql 

    return { loading, error }

   }
   // return a reactive state and the fetch async method
   return { ...toRefs(state), fetchSomething }
  }

Your vue component or page file

<script>
import useSomething from "@/composable-file-name";
import { onMounted } from "vue";

export default {
  setup() {

    // Setting the returned composable values 
    const { my_thing, fetchSomething, loading, error } = useSomething(); 

    // Fetch the data inside the onMounted hook
    onMounted(() => fetchSomething());

    // Return for the view
    return { my_thing, loading, error };
  }
};
</script>

@szvest
Hello, I'd like to use this approach in my code, however I found some issues.

  1. state.my_thing will print _TS2322: Type 'Readonly >>' is not assignable to type 'null'_. I've added 'as any' to useResult.
  2. Component doesn't have access to loading and error. What is the best approach to overcome this? Add them to state?
  3. Is onMounted the only option to start query? I try to use composable result in another composable and I'm expecting loss of reactivity somehow. Second composable runs query with null instead of fetched values, but probably it's because I don't check loading state.

Hi @abishai,

  1. state.my_thing will print _TS2322: Type 'Readonly >>' is not assignable to type 'null'_. I've added 'as any' to useResult.

Yes, my code example wasn't typed. You could fix that in 'ts'.

  1. Component doesn't have access to loading and error. What is the best approach to overcome this? Add them to state?

It should have access if

1 - you import the file of your composable

import useSomething from "@/something";

2 - In your component setup you should return them...

   return { my_thing, loading, error };

... so you can access them in your template as follows :

// Checking for errors
<div v-if="error">
  <h2>{{ error.message }}</h2>
</div>

// While loading
<div v-else-if="loading">
  <h2>Loading...</h2>
</div>

// When there is no error and loading is false
<pre v-else> {{ my_thing }} </pre>
  1. Is onMounted the only option to start query? I try to use composable result in another composable and I'm expecting loss of reactivity somehow. Second composable runs query with null instead of fetched values, but probably it's because I don't check loading state.

onMouted is the right lifecycle hook to fetch remote data (mainly data that require async as fetching an API). That way other stuff in the created hook can already be available. But if your use case is different then you can use other options. In Vue 2 you would use created but since setup function is itself executed at the created hook then you can call the function directly .

@szvest
strange, I have TS2339: Property 'error' does not exist on type '{ fetchData: () => Promise<{ loading: Ref; error: Ref; }>; my_thing: Ref; null: Ref; }'.

I can only import direct useSomething() exports, reactive state and async method. Maybe, another 'ts' issue =/

Was this page helpful?
0 / 5 - 0 ratings