Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using clientId in useSubscription after using provideApolloClients does not work #1494

Open
justDabuK opened this issue Aug 7, 2023 · 3 comments

Comments

@justDabuK
Copy link

justDabuK commented Aug 7, 2023

Describe the bug
I have a vue project. We use the composition API. Therefore we use provideApolloClient in order to provide the client to all usages of useQuery, useMutation, etc. We want to use a second apollo client for a specific new subscription. Switching to provideApolloClients and using clientId as an option leads to an

No apolloClients injection found, tried to resolve 'otherClient' clientId

error.

To Reproduce
On App.vue-level we do in one hook

import { ApolloClients, provideApolloClients } from '@vue/apollo-composable';

export const useProvideApolloClients = () => {
  function createApolloClient(httpLink: string, webSocketLink: string) {
  // does necessary option and setup stuff specific to our clients
  }

 
  const clientA = createApolloClient(clientAHttpLink, clientAWebsockeLink);

  const clientB = createApolloClient(clientAHttpLink, clientAWebsocketLink);

  // This allows-composable apollo to manage the apollo client on its own, no need for providing it via vue
  provideApolloClients({
    default: clientA,
    clientA: clientA,
    clientB: clientB,
  });
};

Further down the road still on App.Vue level, we have subscriptions that use these clients. When I try to use a specific clientId the error appears and the subscription does not work.

import { importantSubscriptionQuery } from '@/queries/important';
import { useSubscription } from '@vue/apollo-composable';

export const useImportantSubscription = () => {

  const { onResult } = useSubscription(acpStartupStatusQuery, null, () => ({
    enabled: state.value,
    clientId: 'clientB',
  }));

  onResult((result) => {
    // do something cool with the result
  });
};

In App.vue we first call useProvideApolloCLients, afterwards we start the subscriptions

<script setup lang="ts">
import { useProvideApolloClients } from '@/hooks/useProvideApolloClients';
import useImportantSubscription from '@/hooks/useImportantSubscription';

useProvideApolloClients();
useImportantSubscription();
</script>

Looking into the console logs, one can see that using the clientId always leads to an No apolloClients injection found, tried to resolve 'otherClient' clientId error. Even when I use "default" as clientId. Somehow it can not find the injected clients object.

Expected behavior
I expected the above setup to work and for the subscription to use "clientB"

Versions
vue: ^3.3.4
@vue/apollo-composable: ^4.0.0-beta.1
@apollo/client: ^3.7.6

Additional context
So far I couldn't find any blog or documentation entry that actually showed how to properly use multiple clients with apollo-composable what you can see above is what I put my self together so far. Therefore, If you know how to properly do this or saw some blogish entry on how to do this, I'd be happy to receive a link to it.

Edit:
Using no clientId does work and leads to no error. Therefore I think it somehow can find the injected clients when default is used by the function itself. But as described above, even when I set clientId to "default" then it still can't find any clients.

@justDabuK justDabuK changed the title Using clientId in useSubscription after using provideApolloCLients does not work Using clientId in useSubscription after using provideApolloClients does not work Aug 7, 2023
@twentyforty
Copy link

twentyforty commented Oct 10, 2023

I was running into the same issue. The ApolloClients don't get injected and the resolveClientId method call here results in an exception.

const providedApolloClients: ClientDict<TCacheShape> | null = inject(ApolloClients, null)
    const providedApolloClient: ApolloClient<TCacheShape> | null = inject(DefaultApolloClient, null)
    resolveImpl = (id?: ClientId) => {
      if (id) {
        const client = resolveClientWithId(providedApolloClients, id)
        if (client) {
          return client
        }
        return resolveClientWithId(savedCurrentClients, id)
      }
      const client = resolveDefaultClient(providedApolloClients, providedApolloClient)
      if (client) {
        return client
      }
      return resolveDefaultClient(savedCurrentClients, savedCurrentClients.default)
    }
function resolveClientWithId<T> (providedApolloClients: ClientDict<T> | null, clientId: ClientId): NullableApolloClient<T> {
  if (!providedApolloClients) {
    throw new Error("No apolloClients injection found, tried to resolve '${clientId}' clientId")
  }
  return providedApolloClients[clientId]
}

To fix this, in my app.vue, I manually provide the apollo clients and bind them to the ApolloClients symbol.

app.vue

provide(ApolloClients, {
  default: useApollo().clients!.default,
  auth: useApollo().clients!.auth,
})

This is an addition to what the docs say about using the providesApolloClients method in the plugin.

import {ApolloClients, provideApolloClient, provideApolloClients} from '@vue/apollo-composable'
export default defineNuxtPlugin(() => {
  provideApolloClients({
    default: useApollo().clients.default,
    auth: useApollo().clients.auth,
  })
})

Seems like the provideApolloClients helper method should this binding for you as well?

@bgaynor78
Copy link

@twentyforty Thanks for the detailed breakdown and a possible solution. I was wondering thought, what is the useApollo() method you mention? I don't find it anywhere in the docs. Is this something you came up with, and if so can you share? Any help would be appreciated as I'm having a very similar issue and haven't found a solution.

This my set up in my app.js

const cache = new InMemoryCache();
const httpLink = createHttpLink({
    uri: `${window.productApiGraphQLEndpoint}/graphql`,
});

const productsHttpLink = createHttpLink({
    uri: `${window.productApiGraphQLEndpoint}/graphql/products`,
});

const authLink = setContext((_, { headers }) => {
    const token = window.productApiKey;

    return {
        headers: {
            ...headers,
            authorization: `Bearer ${token}`
        }
    }
})
const apolloClient = new ApolloClient({
    link: authLink.concat(httpLink),
    cache,
});

const productsApolloClient = new ApolloClient({
    link: authLink.concat(productsHttpLink),
    cache,
})

Then inside of a component later, I use a couple of queries. One should go to the default client, and works without error:

const interceptorQuery = useLazyQuery(gql`
    ...my query stuff, nothing funky
    `
);

Then in the same component I want to execute another query, this time though I want it to go to my "product" client:

const productAccessoriesQuery = useLazyQuery(gql`
    ...more query stuff
    `, {
    partNumber: selectedQuoteItemId,
}, {
    clientId: 'products'
})

This is where the error that was mentioned by @justDabuK occurs for me.

Again, any help anyone can offer would be welcome.

@bgaynor78
Copy link

Found a solution to my problem. I neglected to mention that I was using Inertia in my project to serve up Vue components from the server. It has a slightly different way to set up your app in the app.js. What I had was:

createInertiaApp({
    title: (title) => `${title} | ${appName}`,
    resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
    setup({ el, App, props, plugin }) {
        provideApolloClients({
             default: apolloClient,
             products: productsApolloClient
        })
        return createApp({ render: () => h(App, props) })
            .use(plugin)
            .use(ZiggyVue)
            .use(pinia)
            .component('font-awesome-icon', FontAwesomeIcon)
            .mount(el);
    },

It's appears that Inertia's setup in the createInertiaApp method doesn't function the same as Vue's new Vue({}) method. What I ended up doing was to add another setup() inside the return statement an place the call to provide inside of it like so:

createInertiaApp({
    title: (title) => `${title} | ${appName}`,
    resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
    setup({ el, App, props, plugin }) {
        return createApp({
            setup() {
                provide(ApolloClients, {
                    default: apolloClient,
                    products: productsApolloClient,
                })
            },
            render: () => h(App, props)
        })
            .use(plugin)
            .use(ZiggyVue)
            .use(pinia)
            .component('font-awesome-icon', FontAwesomeIcon)
            .mount(el);
    },

Again, not sure how many use Inertia, but thought I'd report back with what I had to do in order to get my set up to work.

Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants