timhall / svelte-apollo

Svelte integration for Apollo GraphQL
MIT License
947 stars 68 forks source link

Need full example svelte-apollo with sapper #9

Open shashkovdanil opened 5 years ago

shashkovdanil commented 5 years ago

Please add full example svelte-apollo with sapper v3. I'm having trouble with this:

In order to initialize Apollo Client, you must specify link & cache properties on the config object.
        This is part of the required upgrade when migrating from Apollo Client 1.0 to Apollo Client 2.0.
mattpilott commented 5 years ago

I think i also need this it's not completely clear from the sapper example. Be great to see the setup using the stock sapper template!

timhall commented 5 years ago

I had been waiting for the various sapper updates to land for svelte v3 before looking into this, but it looks like I missed the memo and it's been released with v0.26 of sapper. I'll take another look at this soon.

stuffedmotion commented 5 years ago

Would like to see this as well!

jthegedus commented 5 years ago

Apologies for being "that person", but are there any updates on this topic or guidance in the meantime? I have been unable to find a good way of configuring this with Sapper

UPDATE:

I got this working by using _layout.svelte as the point to initialise and set the Client we can then use getClient in each route that uses this layout.

_layout.svelte:

<script>
  import ApolloClient from "apollo-boost";
  import { setClient } from "svelte-apollo";

  const client = new ApolloClient({
    uri: "https://..."
  });
  setClient(client);
</script>

...

index.svelte:

<script>
  import { getClient, query } from 'svelte-apollo'; 
  import { GET_BOOKS } from './queries';

  // 2. Get the Apollo client from context
  const client = getClient();

  // 3. Execute the GET_BOOKS graphql query using the Apollo client
  //    -> Returns a svelte store of promises that resolve as values come in
  const books = query(client, { query: GET_BOOKS });
</script>

<!-- 4. Use $books (note the "$"), to subscribe to query values -->
{#await $books}
  Loading...
{:then result}
  {#each result.data.books as book}
    {book.title} by {book.author.name}
  {/each}
{:catch error}
  Error: {error}
{/await}

This doesn't integrate with the Sapper router, but we can use onMount to perform re-fetches etc. I am yet to try SSR.

jschuhr commented 5 years ago

I took the same approach with _layout.svelte and not just for the svelte-apollo client. Except I put all of that setup into another module (setup.js) and imported from _layout. I just couldn't stomach having all that code actually in my _layout file. It's for layout, supposedly, but it's the only component that is a parent to the whole app.

jthegedus commented 5 years ago

@jschuhr I agree. I am sure this could be done in client.js and server.js but, I myself, do not know how. This was the easiest way forward until this lib integrates with the Sapper primitives.

F7dev commented 5 years ago

@jthegedus when I try to setClient in _layout and then later getClient I get a message: Function called outside component initialization.

The way I get around this is by importing the client directly into the component that calls it. Is this a problem with Svelte's context?

ssiemens-hm commented 5 years ago

@jthegedus when I try to setClient in _layout and then later getClient I get a message: Function called outside component initialization.

The way I get around this is by importing the client directly into the component that calls it. Is this a problem with Svelte's context?

Same issue here:

Error: Function called outside component initialization
    at get_current_component (/git/node_modules/svelte/internal/index.js:520:15)
    at Object.setContext (/git/node_modules/svelte/internal/index.js:550:5)
    at Object.setClient (/git/node_modules/svelte-apollo/dist/svelte-apollo.cjs.js:15:12)
    at create_ssr_component (/git/__sapper__/dev/server/server.js:10007:16)
    at Object.$$render (/git/__sapper__/dev/server/server.js:230:22)
    at create_ssr_component (/git/__sapper__/dev/server/server.js:10182:41)
    at $$render (/git/__sapper__/dev/server/server.js:230:22)
    at Object.render (/git/__sapper__/dev/server/server.js:238:26)
    at handle_page (/git/__sapper__/dev/server/server.js:12560:36)
phi1-h commented 5 years ago

I used the onMount hook around the setClient(), maybe that helps. EDIT: The error reappears when calling the getClient() EDIT 2: Fixed, I imported the child before setClient()!

ssiemens-hm commented 5 years ago

I used the onMount hook around the setClient(), maybe that helps. EDIT: The error reappears when calling the getClient() EDIT 2: Fixed, I imported the child before setClient()!

Cool. Can you provide an example with some code? In which file did you put the setClient()? Where and how did you call the getClient()? Did you need to use the onMount-Function or the

jerriclynsjohn commented 4 years ago

@jthegedus @phi1-h @F7dev @jschuhr @timhall I'm desperately looking to solve this problem, this is a huge show stopper in adopting GraphQL in my sapper project and would love to contribute a template project that works for Sapper. The only thing is I tried everything mentioned in all the issues across this repo and I'm still stranded and seriously tired. I'm forced to use other libraries for PoC for query only, but I wanna exploit the core capabilities.

jthegedus commented 4 years ago

Hi @jerriclynsjohn I don't believe I have done anything special. The only thing of note was that the index route doesn't use the svelte-apollo client, so anything done in _layout.svelte is completely evaluated before I would route elsewhere where client is fetched and used.

vhfmag commented 4 years ago

Hey there! I've just found the same issue and tried the above solutions, but they didn't help me much. After having tried them, I considered avoiding setClient and getClient altogether and simply importing the instantiated client from another module with the hope that the client instance would be reused because of how (I think) modules work.

It doesn't throw anymore and it's working fine so far, but I thought I'd ask people more knowledgeable in Svelte about the consequences of such an approach.

To be clear, there are some sample files:

What am I missing? What are the downsides of such approach?

teds31 commented 4 years ago

Without a proper walkthrough of setting this up, we are gonna be stuck in this boat for a while

simoncpu commented 4 years ago

Greetings. I am an astronaut from the future. I've just landed on the moon via the Apollo spacecraft only to encounter this issue.

simoncpu commented 4 years ago

I gave up trying to make setClient() work on Sapper so I just did something like:

export async function preload() { return { cache: await client.query({ query: EVERYTHING }) }; }

<Component {cache} {client} />

- `Component.svelte`

{#await $preferences} Loading... {:then result} ... {/await}



I'm not sure if I'm doing this correctly. I've just discovered Svelte a few weeks ago.
benmccann commented 4 years ago

Here's a related thread on the Sapper issue tracker that's also helpful: https://github.com/sveltejs/sapper/issues/751

benmccann commented 4 years ago

Is everyone here just doing client-side fetching? Or has anyone gotten this to work on the server-side as well?

Is anyone using Sapper's this.fetch? If not, do you still have the request headers from the original request carried over to pass cookies and auth headers?

ajbouh commented 4 years ago

I'm doing server and client-side. Am not currently using this.fetch, though that's mostly because I forgot it existed.

I have a middleware that extracts the bearer token from the session (on both client and server) and adds it as an authorization header in the fetch request.

This approach isn't perfect but it works reasonably well. At some point I will probably use the cache from the server request to the client by passing it in the session object.

Gh05d commented 4 years ago

I had the same problem and was able to work around the limitations using the context api. Sharing is caring, so I hope this helps somebody:

// _layout.svelte
<script context="module">
  import { ApolloClient } from "apollo-client";
  import { createHttpLink } from "apollo-link-http";
  import { InMemoryCache } from "apollo-cache-inmemory";
  import { RetryLink } from "apollo-link-retry";

  export async function preload(page, session) {
    const httpLink = createHttpLink({
      // Neccessary because it is a json property which is a string
      uri: `http${process.env.BACKEND_SSL ? "s" : ""}://${
        process.env.SERVER_NAME
      }:${process.env.SERVER_PORT}/graphql`,
      credentials: "include",
      fetch: this.fetch
    });

    const retryLink = new RetryLink({
      attempts: { max: 3 },
      delay: { initial: 200 }
    });

    const link = retryLink.concat(httpLink);

    const client = new ApolloClient({
      link,
      cache: new InMemoryCache(),
      name: "Some Website",
      version: "3.0"
    });

    return { client };
  }
</script>

<script>
  import { setContext } from "svelte";

  export let client;
  setContext("client", client);
</script>

// component.svelte
<script>
  import { getContext } from "svelte";
  const client = getContext("client");
</script>
ajbouh commented 4 years ago

Based on my reading of https://sapper.svelte.dev/docs#Preloading I expect the { client } return value from preload to fail serialization with deval. It seems like returning client from preload will (silently?) fail serialization of that page's preload values.

Doesn't seem to matter for the exact example you've given, but perhaps this fact will help someone else trying to tease apart why they're seeing preload run once on the server and then again in the browser in their own application.

On Thu, Jun 18, 2020 at 6:54 AM Pascal Clanget notifications@github.com wrote:

I had the same problem and was able to work around the limitations using the context api. Sharing is caring, so I hope this helps somebody:

// _layout.svelte

// component.svelte

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/timhall/svelte-apollo/issues/9#issuecomment-646031442, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAABZHNLXPBMQBIVTYOB64LRXIL73ANCNFSM4HJOYRXQ .

dopry commented 4 years ago

re: Function called outside component initialization

this happens with getClient and setClient because it is a svelte context which is only available at component initialization (construction) and cannot be in an event handler. see: the answer for https://stackoverflow.com/questions/60591927/svelte-user-registration-issue-with-setting-store-value Most likely what is happening is that you're calling it in an async event su as an on:click, onMount, onDestroy when it is not available. The solution to this is to store your client as a store in the context as per https://svelte.dev/tutorial/context-api.

In general it should probably look something like:

setClient(writable(apolloClient));
let client
getClient().subscribe((_client) => client = _client); 
imCorfitz commented 3 years ago

I would just like to share this here. I made this about a year ago, and just recently updated it. Sapper start aside, I believe it serves well as an example of SSR, setClient etc.

rodshtein commented 3 years ago

I would just like to share this here. I made this about a year ago, and just recently updated it. Sapper start aside, I believe it serves well as an example of SSR, setClient etc.

👉   https://github.com/timhall/svelte-apollo/issues/59 👈   👀

I just use apollo/core

ticruz38 commented 3 years ago

Using this package with a generator like this https://github.com/ticruz38/graphql-codegen-svelte-apollo, could resolve most of your issues when working with Sapper. I think the client side rehydration is not that important, worst case you'll end up making 2 queries instead of one, your page will be server side rendered with your data anyway... Last version of this package broke the sapper compatibility however... https://github.com/ticruz38/graphql-codegen-svelte-apollo/issues/2

oxdog commented 3 years ago

https://bjornlu.com/blog/using-apollo-client-in-sapper/

This approach works well. On the server it writes the cache into the session and on the client replaces it with a new apollo client with the cache data from the server.

But the code in the link is not 100% working. Just apply the following:

Add apollo client a bit differently to the session


import fetch from 'cross-fetch'
...
sapper.middleware({
    session: (req) => {

      const apollo = new ApolloClient({
        ssrMode: true,
        link: createHttpLink({
          uri: 'your-endpoint-uri',
          credentials: 'same-origin',
          fetch
        }),
        cache: new InMemoryCache()
      })

      return {
        apollo
      }
    }
  })

also make sure you check if in the preload before you do a call it is on the server (preload runs on server and client)

<script context="module">
  export async function preload(page, session) {
    const YOUR-QUERY = gql`
      query yourQuery{
        ...
      }
    `

    if (!process.browser) {
      const { apollo } = session

      const data = await apollo.query({ query: YOUR-QUERY })
    }
  }
</script>

The rest is well desribed in the link above.

imCorfitz commented 3 years ago

With the transition towards Svelte Kit, maybe such example would be ideal to look at as well.

kwiat1990 commented 3 years ago

@nico-on-vacation, I have tried to set up Apollo Client in my Sapper project just like you described but sadly, all I can get is the following error: Failed to serialize session data: Cannot stringify arbitrary non-POJOs. Does anyone have an idea what's wrong and how it could be fixed?