nuxt-modules / apollo

Nuxt.js module to use Vue-Apollo. The Apollo integration for GraphQL.
https://apollo.nuxtjs.org
MIT License
940 stars 198 forks source link

Example usage with Vue Composition API #288

Open lewebsimple opened 4 years ago

lewebsimple commented 4 years ago

What problem does this feature solve?

Vue-Apollo 4.x.x alpha release is based on the new Vue Composition API. Would be great to be able to use it with @nuxtjs/apollo

This feature request is available on Nuxt community (#c267)
kieusonlam commented 4 years ago

I will take a look at that.

Linus-Boehm commented 4 years ago

Any progress on this? Tried the apollo module with the new useQuery and get a Error: Apollo Client with id default not found

ozum commented 4 years ago

By the way, is there a workaround to use @nuxtjs/apollo in a nuxt project which uses composition API with @vue/composition-api?

negezor commented 4 years ago

I found an option how to use @vue/apollo-composable. Need to perform a couple of simple steps: Install @vue/apollo-composable, create plugin provide-apollo-client.ts (name can be any) with the following content:

// plugins/provide-apollo-client.js
import { provide } from '@vue/composition-api';
import { DefaultApolloClient } from '@vue/apollo-composable';

export default ({ app }) => {
    app.setup = () => {
        const apolloClient = app.apolloProvider.defaultClient;

        provide(DefaultApolloClient, apolloClient);
    }
}

Specify it in the config with the main API

// nuxt.config.js
plugins: [
    '@/plugins/composition-api',
    '@/plugins/provide-apollo-client'
],

The rest of the configuration is no different. However, there are still unsolved problems with SSR errors (example: 404 status code).

lewebsimple commented 4 years ago

@negezor I think assigning app.setup = () => { ... } could be a problem if more than one Nuxt plugin want to customize app.setup ... i.e. this is not composable in itself and raises a TS error Type '() => void' is not assignable to type 'SetupFunction<Data, Data>'.

Maybe injecting apolloClient the usual "nuxt way" could work ... I also asked a related question on Discord.

This points to a missing feature of Nuxt for making plugins work nicely with the Composition API.

kazazes commented 4 years ago

Following @negezor's plugin, (@lewebsimple and in a more nuxy way via nuxt-composition-api):

// ~/plugins/apollo/provide.ts
import { provide, reactive } from "nuxt-composition-api";
import { Context, Plugin } from "@nuxt/types";
import { ApolloClients } from "@vue/apollo-composable";

const provideApollo: Plugin = ({ app }: Context) => {
  app.setup = () => {
    // since plugin is eval'd after module app.apolloProvider will always be defined
    const clients = reactive(app.apolloProvider?.clients); 
    provide(DefaultApolloClient, clients?.defaultClient);
    // not functionally important but req'd return type
    return { [ApolloClients]: clients }; 
  };
};

export default provideApollo;

One important thing to note is that @vue/apollo-composable expects a config named default, whereas @nuxtjs/apollo's default config is named defaultClient. If you provide ApolloClients vs. DefaultApolloClient, you must use { clientId: "defaultConfig" } as an option when calling apollo-composeable.

<template>
  {{ result.company.name }}
</template>
<script lang="ts">
  import { Component, Vue } from "nuxt-property-decorator";
  import "vue-apollo"; // just noticed this is here, don't recall why...
  import { useCompanyQuery } from "@/generated/graphql-codegen";

  @Component({
    setup: () => {
      const { result, loading, error } = useCompanyQuery({
        where: { isParent: true }
      });

      return { result, loading, error };
    }
  })
  export default class CompaniesCard extends Vue {}
</script>

NB: Companies and Company are different queries.

~For fluency's sake defaultClient needs to be renamed default for interop. with @vue/apollo-composable.~

Edit: Weirdly enough vue-apollo does use defaultClient, and @vue/apollo-composeable uses a different property name.

tcastelly commented 4 years ago

Hi,

I'm pretty sure I made a mistake somewhere ... I have this error displayed:

const useFetch = (callback) => {
    var _a;
    const vm = getCurrentInstance();
    if (!vm)
        throw new Error('This must be called within a setup function.');
    registerCallback(vm, callback);
    if (typeof vm.$options.fetchOnServer === 'function') {
        vm._fetchOnServer = vm.$options.fetchOnServer.call(vm) !== false;
    }

nuxt.config.js

  buildModules: [
    '@nuxt/typescript-build',
    'nuxt-composition-api',
  ],
  modules: [
    '@nuxtjs/apollo',
  ],
  plugins: [
    '~/plugins/provideApolloClient.ts',
  ],
  apollo: {
 ...

./plugins/provideApolloClient.ts

import { provide, reactive } from 'nuxt-composition-api';
import { Context, Plugin } from '@nuxt/types';
import { DefaultApolloClient } from '@vue/apollo-composable';

const provideApollo: Plugin = ({ app }: Context) => {
  app.setup = () => {
    // since plugin is eval'd after module app.apolloProvider will always be defined
    const clients = reactive(app.apolloProvider?.clients);
    provide(DefaultApolloClient, clients?.defaultClient);
    // not functionally important but req'd return type
    return { [DefaultApolloClient]: clients };
  };
};

export default provideApollo;

index.vue

import 'vue-apollo'; // just noticed this is here, don't recall why...

package.json

  "dependencies": {
    "@nuxt/types": "^2.13.3",
    "@vue/apollo-composable": "^4.0.0-alpha.8",
    "graphql-tag": "^2.10.4",
    "nuxt-composition-api": "^0.10.6",
  },
  "devDependencies": {
    "@nuxtjs/apollo": "^4.0.1-rc.1",

The complet projext: https://github.com/shenron/vue-nuxt-tsx

Has someone a clue? Thank you very much.

Edit: Now I don't have errors, just a the page is in pending. The same query works with @nuxt/apollo without composition. Edit 2: The last version of nuxt-composition-api works with this is: "nuxt-composition-api": "^0.8.2"

NickBolles commented 4 years ago

There's an update to nuxt-composition-api that provides an onGlobalSetup hook. So now the plugin can just be (typescript)

import { Context } from "@nuxt/types";
import { provide, onGlobalSetup, defineNuxtPlugin } from "nuxtjs/composition-api";
import { DefaultApolloClient } from "@vue/apollo-composable";

/**
 * This plugin will connect @nuxt/apollojs with @vue/apollo-composable
 */
export default defineNuxtPlugin({ app }: Context): void => {
  onGlobalSetup(() => {
    provide(DefaultApolloClient, app.apolloProvider?.defaultClient);
  });
});

and then of course add the plugin

plugins: ["@/plugins/apollo/plugin.ts"]

edit: use @nuxtjs/composition-api and defineNuxtPlugin

toddheslin commented 4 years ago

Just a note from @NickBolles response (which worked perfect for me), the nuxt composition api library is now @nuxtjs/composition-api' so it should be:

import { provide, onGlobalSetup } from '@nuxtjs/composition-api'

NickBolles commented 4 years ago

Thanks @toddheslin! I've updated the example above to use the new @nuxtjs/composition-api and the defineNuxtPlugin helper too.

manniL commented 3 years ago

Fixed up example

import { Context } from '@nuxt/types';
import { provide, onGlobalSetup, defineNuxtPlugin } from "@nuxtjs/composition-api";
import { DefaultApolloClient } from "@vue/apollo-composable";

/**
 * This plugin will connect @nuxt/apollojs with @vue/apollo-composable
 */

export default defineNuxtPlugin(({ app }: Context): void => {
  onGlobalSetup(() => {
    provide(DefaultApolloClient, app.apolloProvider?.defaultClient);
  });
})
Rasool-deldar commented 3 years ago

Fixed up example

import { Context } from '@nuxt/types';
import { provide, onGlobalSetup, defineNuxtPlugin } from "@nuxtjs/composition-api";
import { DefaultApolloClient } from "@vue/apollo-composable";

/**
 * This plugin will connect @nuxt/apollojs with @vue/apollo-composable
 */

export default defineNuxtPlugin(({ app }: Context): void => {
  onGlobalSetup(() => {
    provide(DefaultApolloClient, app.apolloProvider?.defaultClient);
  });
})

My project has an error, please tell me how to solve this problem

Cannot read property 'DefaultApolloClient' of undefined

nuxt - 2.15.3 @nuxtjs/apollo - 4.0.1-rc.5 @nuxtjs/composition-api - 0.22.1 @vue/apollo-composable - 4.0.0-alpha.12

Console error ERROR [Vue warn]: Error in data(): "TypeError: Cannot read property 'DefaultApolloClient' of undefined"

toddheslin commented 3 years ago

@Rasool-deldar try @nuxtjs/composition-api - @0.21.0

Rasool-deldar commented 3 years ago

@Rasool-deldar try @nuxtjs/composition-api - @0.21.0

install @nuxtjs/composition-api - 0.21.0 & 0.20.0

It still shows this error - Cannot read property 'DefaultApolloClient' of undefined

link test ( display error ) - https://codesandbox.io/s/elastic-satoshi-otjer

toddheslin commented 3 years ago

@Rasool-deldar That link wasn't working but here is what I'm currently using:

"@nuxtjs/apollo": "^4.0.1-rc.5",
"@nuxtjs/composition-api": "^0.21.0",
"@vue/apollo-composable": "^4.0.0-alpha.12"

Now I remember what I had to change to get it to this point...

1) transpile in nuxt.config.js

/*
  ** Build configuration
  */
build: {
  transpile: ['@vue/apollo-composable'],    
},

Then, replace all references to import like this (from /dist):

import { DefaultApolloClient } from '@vue/apollo-composable/dist'

That's what I needed to do to move from an earlier version (I've been using it for about a year) till the latest ones)

Hope this helps!

Rasool-deldar commented 3 years ago

dist

import { DefaultApolloClient } from '@vue/apollo-composable/dist'

Thank you for your help.

I have used this method

import { DefaultApolloClient } from '@vue/apollo-composable'

I have replaced it with the following method

import { DefaultApolloClient } from '@vue/apollo-composable/dist'

I tested it on this version, it worked 0.21.0 & 0.22.3 @nuxtjs/apollo": "^4.0.1-rc.5", "@nuxtjs/composition-api": "^0.21.0", "@vue/apollo-composable": "^4.0.0-alpha.12"

Rasool-deldar commented 3 years ago

@Rasool-deldar That link wasn't working but here is what I'm currently using:

"@nuxtjs/apollo": "^4.0.1-rc.5",
"@nuxtjs/composition-api": "^0.21.0",
"@vue/apollo-composable": "^4.0.0-alpha.12"

Now I remember what I had to change to get it to this point...

  1. transpile in nuxt.config.js
/*
  ** Build configuration
  */
build: {
  transpile: ['@vue/apollo-composable'],    
},

Then, replace all references to import like this (from /dist):

import { DefaultApolloClient } from '@vue/apollo-composable/dist'

That's what I needed to do to move from an earlier version (I've been using it for about a year) till the latest ones)

Hope this helps!

how use ssr ? .

not work ssr useQuery( Test, {}, { prefetch: true, } );

https://codesandbox.io/s/falling-glitter-t4ko2?file=/pages/index.vue:848-938

toddheslin commented 3 years ago

SSR should just work out of the box. When the page is loaded from the server, the GQL request is made server-side, otherwise it's client.

Not sure what issue you're facing here but glad to know the config changes got the upgrade to work 👍

Rasool-deldar commented 3 years ago

SSR should just work out of the box. When the page is loaded from the server, the GQL request is made server-side, otherwise it's client.

Not sure what issue you're facing here but glad to know the config changes got the upgrade to work 👍

I do not know exactly how to handle ssr. Without waiting for the data to go to the server, it goes to the data client, and there it says, please help me, how can I solve this problem? ( localhost - npm run dev )

Look at this link below in the terminal and console, you will see the undefined value on the server side, then the data value will return to the user side.

nuxt-mode ssr: true,

https://codesandbox.io/s/falling-glitter-t4ko2?file=/pages/index.vue:848-938

toddheslin commented 3 years ago

OK I see your problem here. You can't use async functions in a vue 2.x setup function (ie current version of nuxt). I'll add some commentary:

const movies = ref([]);

// No point doing async/await here because the callback function is called after data is resolved
onResult(async ({ data }) => {
    movies.value = await data.movies;
});

// this will always be undefined because we aren't blocking the component from rendering above (and can't)
console.log(result.value);

// same as above
console.log(movies.value);

// setup() is called on both server and client, so it's a bit misleading to log 'ssr' here
console.log("ssr");

The correct way to do it is this:

<script>
import { defineComponent } from "@nuxtjs/composition-api";
import { useQuery, useResult } from "@vue/apollo-composable/dist";

export default defineComponent({
  components: {
    Logo,
    IconLink,
  },
  setup() {
    // not work ssr
    const { result, loading, error } = useQuery(
      Test,
      {},
      {
        fetchPolicy: "no-cache",
        prefetch: true,
      }
    );
    const movies = useResult(result)

    return {
      movies,
      error,
      loading,
    };
  },
});
</script>

In your <template> you can render out movies if we aren't loading or if there is no error. The only time you need to really use onResult() (which I do use a lot) is where you are setting module-scoped refs from a composable that need to be shared state between different components. This is more of an advanced case when you're feeling comfortable with the above.

Check out useResult here: https://v4.apollo.vuejs.org/guide-composable/query.html#useresult

Rasool-deldar commented 3 years ago

SSR should just work out of the box. When the page is loaded from the server, the GQL request is made server-side, otherwise it's client. Not sure what issue you're facing here but glad to know the config changes got the upgrade to work 👍

I do not know exactly how to handle ssr. Without waiting for the data to go to the server, it goes to the data client, and there it says, please help me, how can I solve this problem? ( localhost - npm run dev )

Look at this link below in the terminal and console, you will see the undefined value on the server side, then the data value will return to the user side.

nuxt-mode ssr: true,

https://codesandbox.io/s/falling-glitter-t4ko2?file=/pages/index.vue:848-938 Thank you very much for your help

OK I see your problem here. You can't use async functions in a vue 2.x setup function (ie current version of nuxt). I'll add some commentary:

const movies = ref([]);

// No point doing async/await here because the callback function is called after data is resolved
onResult(async ({ data }) => {
    movies.value = await data.movies;
});

// this will always be undefined because we aren't blocking the component from rendering above (and can't)
console.log(result.value);

// same as above
console.log(movies.value);

// setup() is called on both server and client, so it's a bit misleading to log 'ssr' here
console.log("ssr");

The correct way to do it is this:

<script>
import { defineComponent } from "@nuxtjs/composition-api";
import { useQuery, useResult } from "@vue/apollo-composable/dist";

export default defineComponent({
  components: {
    Logo,
    IconLink,
  },
  setup() {
    // not work ssr
    const { result, loading, error } = useQuery(
      Test,
      {},
      {
        fetchPolicy: "no-cache",
        prefetch: true,
      }
    );
    const movies = useResult(result)

    return {
      movies,
      error,
      loading,
    };
  },
});
</script>

In your <template> you can render out movies if we aren't loading or if there is no error. The only time you need to really use onResult() (which I do use a lot) is where you are setting module-scoped refs from a composable that need to be shared state between different components. This is more of an advanced case when you're feeling comfortable with the above.

Check out useResult here: https://v4.apollo.vuejs.org/guide-composable/query.html#useresult

Thank you very much for your help. 👍 👍 👍

jcjp commented 3 years ago

Anybody experienced the error of TypeError: defaultClientConfig is undefined here is my plugin:

import {Context} from '@nuxt/types'
import {
  provide,
  onGlobalSetup,
  defineNuxtPlugin
} from '@nuxtjs/composition-api'
import {DefaultApolloClient} from '@vue/apollo-composable/dist'

/**
 * This plugin will connect @nuxt/apollojs with @vue/apollo-composable
 */

export default defineNuxtPlugin(({app}: Context): void => {
  onGlobalSetup(() => {
    provide(DefaultApolloClient, app.apolloProvider?.defaultClient)
  })
})

I also added the transpile configuration on my nuxt.config.js

build: {
  ...
  transpile: ['@vue/apollo-composable']
},
Tomaszal commented 3 years ago

I have tried setting it up as described by others in this issue. However, I am currently having trouble with the Nuxt client throwing the following error: "Apollo client with id default not found. Use provideApolloClient() if you are outside of a component setup."

Initially it renders the page correctly with the GraphQL query being executed correctly on the server, but it seems to fail after it gets hydrated. Here is a repository to reproduce the issue: https://github.com/Tomaszal/nuxt-apollo-composable-test

After some debugging, it seems that this resolveClient function is being called with an undefined value injecting ApolloClients and DefaultApolloClient returns null on the Nuxt client, but I cannot figure out why. Perhaps someone knows better why this is happening?

EDIT: Was a silly mistake. I copied the code for the plugin with DefaultApolloClient being imported from '@vue/apollo-composable', but I imported the composition functions from '@vue/apollo-composable/dist' instead. If anyone has the same issue make sure to import everything from the same place, as DefaultApolloClient is a unique symbol. Not sure if the '/dist' part is still necessary, for me it works without it.

Davidnadejdin commented 2 years ago

I have tried setting it up as described by others in this issue. However, I am currently having trouble with the Nuxt client throwing the following error: "Apollo client with id default not found. Use provideApolloClient() if you are outside of a component setup."

Initially it renders the page correctly with the GraphQL query being executed correctly on the server, but it seems to fail after it gets hydrated. Here is a repository to reproduce the issue: https://github.com/Tomaszal/nuxt-apollo-composable-test

After some debugging, it seems that ~this resolveClient function is being called with an undefined value~ injecting ApolloClients and DefaultApolloClient returns null on the Nuxt client, but I cannot figure out why. Perhaps someone knows better why this is happening?

EDIT: Was a silly mistake. I copied the code for the plugin with DefaultApolloClient being imported from '@vue/apollo-composable', but I imported the composition functions from '@vue/apollo-composable/dist' instead. If anyone has the same issue make sure to import everything from the same place, as DefaultApolloClient is a unique symbol. Not sure if the '/dist' part is still necessary, for me it works without it.

import { Context } from '@nuxt/types'
import {
  onGlobalSetup,
  defineNuxtPlugin
} from '@nuxtjs/composition-api'
// @ts-ignore
import { provideApolloClient } from '@vue/apollo-composable'

/**
 * This plugin will connect @nuxt/apollojs with @vue/apollo-composable
 */

export default defineNuxtPlugin(({ app }: Context): void => {
  onGlobalSetup(() => {
    provideApolloClient(app.apolloProvider?.defaultClient)
  })
})
thisismydesign commented 1 year ago

With Nuxt 2 Bridge this is how I defined the provider, see: https://stackoverflow.com/q/73986807/2771889

import { provideApolloClient } from '@vue/apollo-composable'

export default defineNuxtPlugin((nuxtApp) => {
  provideApolloClient(nuxtApp.apolloProvider?.defaultClient)
})