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

Need full example svelte-apollo with sapper #9

Open
shashkovdanil opened this issue Apr 30, 2019 · 29 comments
Open

Need full example svelte-apollo with sapper #9

shashkovdanil opened this issue Apr 30, 2019 · 29 comments

Comments

@shashkovdanil
Copy link

shashkovdanil commented Apr 30, 2019

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
Copy link

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
Copy link
Owner

timhall commented May 29, 2019

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
Copy link

Would like to see this as well!

@jthegedus
Copy link

jthegedus commented Jul 23, 2019

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
Copy link

jschuhr commented Jul 27, 2019

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
Copy link

@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
Copy link

F7dev commented Aug 27, 2019

@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
Copy link

@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
Copy link

phi1-h commented Sep 3, 2019

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
Copy link

ssiemens-hm commented Sep 3, 2019

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 <script context="module">-tag? Every help is much appreciated as the original-tutorial is quite confusing.

@jthegedus
Copy link

jthegedus commented Sep 3, 2019

Hi all, here's what I have working:

setClient in _layout.svelte
<script>
  import { stores } from "@sapper/app";
  import { onMount } from "svelte";
  import ApolloClient from "apollo-boost";
  import { setClient } from "svelte-apollo";

  import Nav from "components/Nav.svelte";
  import NProgress from "nprogress";

  const { preloading } = stores();

  onMount(async () => {
    NProgress.configure({ parent: "#sapper", showSpinner: false });
    preloading.subscribe(loading => {
      if (loading) {
        NProgress.start();
      } else {
        NProgress.done();
      }
    });
  });

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

<style>
  main {
    position: relative;
    background-color: white;
    margin: 0 auto;
    box-sizing: border-box;
  }
</style>

<Nav />

<main>
  <slot />
</main>
getClient in routes/some_route/index.svelte
<script>
  import { gql } from "apollo-boost";
  import { getClient, query } from "svelte-apollo";

  const client = getClient();
  const GET_DATA = gql`
    query {
      ...
    }
  `;
  const data = query(client, { query: GET_DATA });
</script>

<div>
	{#await $data}
		<div>loading</div>
	{:then result}
		<div>{result}</div>
	{:catch error}
		<div>Error: {error}</div>
	{/await}
</div>

Not sure I did anything special here other than not use getClient in routes/index.svelte

@jerriclynsjohn
Copy link

@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
Copy link

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
Copy link

vhfmag commented Apr 11, 2020

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:

  • src/data/client.js
    import ApolloClient from "apollo-boost";
    import fetch from "isomorphic-unfetch";
    
    export const client = new ApolloClient({ fetch, uri: "..." });
  • src/routes/index.svelte
    <script context="module">
      import { gql } from "apollo-boost";
      import { client } from "../data/client";
    
      const indexQuery = gql`query {
        # stuff
      }`;
    
      export async function preload() {
        return { cache: await client.query({ query: indexQuery }) };
      }
    </script>
    
    <script>
      import { restore, query } from "svelte-apollo";
    
      export let cache;
      restore(client, indexQuery, cache.data);
    
      const companiesPromise = query(client, { query: indexQuery });
    </script>
    
    {#await $companiesPromise}
      <div>Loading</div>
    {:then companies}
      <pre>{JSON.stringify(companies)}</pre>
    {:catch error}
      <div>Error: {error}</div>
    {/await}

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

@teds31
Copy link

teds31 commented Apr 15, 2020

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

@simoncpu
Copy link

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
Copy link

simoncpu commented May 12, 2020

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

  • _layout.svelte
<script context="module">
import client, { EVERYTHING } from '../../lib/apollo.js';

export async function preload() {
    return {
        cache: await client.query({ query: EVERYTHING })
    };
}
</script>
<script>
export let cache;
</script>
<Component {cache} {client} />
  • Component.svelte
<script>
import { restore, query } from 'svelte-apollo';
import { EVERYTHING } from '../lib/apollo.js';

export let cache;
export let client;
restore(client, EVERYTHING, cache.data);

const preferences = query(client, { query: EVERYTHING });
</script>
{#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
Copy link

benmccann commented May 28, 2020

Here's a related thread on the Sapper issue tracker that's also helpful: sveltejs/sapper#751

@benmccann
Copy link

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
Copy link

ajbouh commented May 28, 2020

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
Copy link

Gh05d commented Jun 18, 2020

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
Copy link

ajbouh commented Jun 18, 2020 via email

@dopry
Copy link

dopry commented Jun 18, 2020

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
Copy link

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
Copy link

rodshtein commented Oct 29, 2020

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.

👉   #59 👈   👀

I just use apollo/core

@ticruz38
Copy link

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... ticruz38/graphql-codegen-svelte-apollo#2

@oxdog
Copy link

oxdog commented Jun 23, 2021

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
Copy link

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

@kwiat1990
Copy link

@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?

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