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

WIP feat: apollo-client@2 → @apollo/client@3 #132

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

sotnikov-link
Copy link

@sotnikov-link sotnikov-link commented Apr 21, 2020

Move from apollo-client@2 to @apollo/client@3 by manual:
https://www.apollographql.com/docs/react/v3.0-beta/migrating/apollo-client-3-migration/

@sotnikov-link sotnikov-link changed the title feat: apollo-client@2 → @apollo/client@3 WIP feat: apollo-client@2 → @apollo/client@3 Apr 22, 2020
@sotnikov-link
Copy link
Author

sotnikov-link commented Apr 22, 2020

Changes work for my project over yarn link.

How to fix tests? :)

image

yarn run v1.13.0
$ yarn tslint && yarn build && jest
$ tslint -c tslint.json -p tsconfig.json -t codeFrame
$ tsc
 PASS  integration/using-app-no-ssr/index.test.ts (11.847s)
  ● Console

    console.log integration/next-test-utils.ts:69
      Running command "next build /Users/Valeriy/Workspaces/VTB/next-with-apollo/integration/using-app-no-ssr"

 FAIL  integration/using-app/index.test.ts (11.876s)
  ● Console

    console.log integration/next-test-utils.ts:69
      Running command "next build /Users/Valeriy/Workspaces/VTB/next-with-apollo/integration/using-app"

  ● Using _app › react-apollo support › loads <Query /> data on the server

    expect(received).toContain(expected) // indexOf

    Expected substring: "<p>Next Apollo</p>"
    Received string:    "<!DOCTYPE html><html><head><meta charSet=\"utf-8\"/><meta name=\"viewport\" content=\"width=device-width,minimum-scale=1,initial-scale=1\"/><meta name=\"next-head-cou
nt\" content=\"2\"/><link rel=\"preload\" href=\"/_next/static/vvRYybnUAnpoCcdtTMKi4/pages/index.js\" as=\"script\"/><link rel=\"preload\" href=\"/_next/static/vvRYybnUAnpoCcdtTMKi4/pages/_ap
p.js\" as=\"script\"/><link rel=\"preload\" href=\"/_next/static/runtime/webpack-035ac2b14bde147cb4a8.js\" as=\"script\"/><link rel=\"preload\" href=\"/_next/static/chunks/commons.43c2bb084f9
a1f33defd.js\" as=\"script\"/><link rel=\"preload\" href=\"/_next/static/runtime/main-e56967ff532601537c41.js\" as=\"script\"/></head><body><div id=\"__next\"><p>loading</p></div><script id=\
"__NEXT_DATA__\" type=\"application/json\">{\"dataManager\":\"[]\",\"props\":{\"apolloState\":{\"data\":{\"User:uniqueid\":{\"__typename\":\"User\",\"id\":\"uniqueid\",\"name\":\"Next Apollo\"},\"ROOT_QUERY\":{\"__typename\":\"Query\",\"hire\":{\"__ref\":\"User:uniqueid\"}}}},\"apollo\":null},\"page\":\"/\",\"query\":{},\"buildId\":\"vvRYybnUAnpoCcdtTMKi4\"}</script><script nomodule=\"\" src=\"/_next/static/runtime/polyfills-38a99feee50c8bacb1bb.js\"></script><script async=\"\" data-next-page=\"/\" src=\"/_next/static/vvRYybnUAnpoCcdtTMKi4/pages/index.js\"></script><script async=\"\" data-next-page=\"/_app\" src=\"/_next/static/vvRYybnUAnpoCcdtTMKi4/pages/_app.js\"></script><script src=\"/_next/static/runtime/webpack-035ac2b14bde147cb4a8.js\" async=\"\"></script><script src=\"/_next/static/chunks/commons.43c2bb084f9a1f33defd.js\" async=\"\"></script><script src=\"/_next/static/runtime/main-e56967ff532601537c41.js\" async=\"\"></script></body></html>"

      43 |     it('loads <Query /> data on the server', async () => {
      44 |       const html = await renderViaHTTP(appPort, '/');
    > 45 |       expect(html).toContain('<p>Next Apollo</p>');
         |                    ^
      46 | 
      47 |       const { apolloState } = extractNextData(html);
      48 |       expect(apolloState).toMatchSnapshot();

      at using-app/index.test.ts:45:20
      at step (using-app/index.test.ts:33:23)
      at Object.next (using-app/index.test.ts:14:53)
      at fulfilled (using-app/index.test.ts:5:58)

  ● Using _app › @apollo/react-hooks support › loads useQuery data on the server

    expect(received).toContain(expected) // indexOf

    Expected substring: "<p>Next Apollo</p>"
    Received string:    "<!DOCTYPE html><html><head><meta charSet=\"utf-8\"/><meta name=\"viewport\" content=\"width=device-width,minimum-scale=1,initial-scale=1\"/><meta name=\"next-head-count\" content=\"2\"/><link rel=\"preload\" href=\"/_next/static/vvRYybnUAnpoCcdtTMKi4/pages/hooks.js\" as=\"script\"/><link rel=\"preload\" href=\"/_next/static/vvRYybnUAnpoCcdtTMKi4/pages/_app.js\" as=\"script\"/><link rel=\"preload\" href=\"/_next/static/runtime/webpack-035ac2b14bde147cb4a8.js\" as=\"script\"/><link rel=\"preload\" href=\"/_next/static/chunks/commons.43c2bb084f9a1f33defd.js\" as=\"script\"/><link rel=\"preload\" href=\"/_next/static/runtime/main-e56967ff532601537c41.js\" as=\"script\"/></head><body><div id=\"__next\"><p>loading</p></div><script id=\"__NEXT_DATA__\" type=\"application/json\">{\"dataManager\":\"[]\",\"props\":{\"apolloState\":{\"data\":{\"User:uniqueid\":{\"__typename\":\"User\",\"id\":\"uniqueid\",\"name\":\"Next Apollo\"},\"ROOT_QUERY\":{\"__typename\":\"Query\",\"hire\":{\"__ref\":\"User:uniqueid\"}}}},\"apollo\":null},\"page\":\"/hooks\",\"query\":{},\"buildId\":\"vvRYybnUAnpoCcdtTMKi4\"}</script><script nomodule=\"\" src=\"/_next/static/runtime/polyfills-38a99feee50c8bacb1bb.js\"></script><script async=\"\" data-next-page=\"/hooks\" src=\"/_next/static/vvRYybnUAnpoCcdtTMKi4/pages/hooks.js\"></script><script async=\"\" data-next-page=\"/_app\" src=\"/_next/static/vvRYybnUAnpoCcdtTMKi4/pages/_app.js\"></script><script src=\"/_next/static/runtime/webpack-035ac2b14bde147cb4a8.js\" async=\"\"></script><script src=\"/_next/static/chunks/commons.43c2bb084f9a1f33defd.js\" async=\"\"></script><script src=\"/_next/static/runtime/main-e56967ff532601537c41.js\" async=\"\"></script></body></html>"

      53 |     it('loads useQuery data on the server', async () => {
      54 |       const html = await renderViaHTTP(appPort, '/hooks');
    > 55 |       expect(html).toContain('<p>Next Apollo</p>');
         |                    ^
      56 | 
      57 |       const { apolloState } = extractNextData(html);
      58 |       expect(apolloState).toMatchSnapshot();

      at using-app/index.test.ts:55:20
      at step (using-app/index.test.ts:33:23)
      at Object.next (using-app/index.test.ts:14:53)
      at fulfilled (using-app/index.test.ts:5:58)

Test Suites: 1 failed, 1 passed, 2 of 4 total
Tests:       2 failed, 4 passed, 6 total
Snapshots:   0 total
Time:        12.209s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

@mvantoorn
Copy link

What's the progress on this issue, would like to upgrade my application to Apollo Client 3.0

@sotnikov-link
Copy link
Author

@mvantoorn
Hello! I use https://www.npmjs.com/package/@sotnikov/next-with-apollo it works.

@shihfu
Copy link

shihfu commented Jul 26, 2020

What's the status of this pull request?

@chemicalkosek
Copy link

next-with-apollo works fine with Apollo Client 3.0. It's independent of apollo version. What's your problem with Apollo 3? Doesn't work?

@borekb
Copy link

borekb commented Jul 27, 2020

@chemicalkosek The current version (5.1.0) still imports from packages like apollo-client, not @apollo/client – I'm getting e.g. various TypeScript errors. @sotnikov-link's fork resolves that.

@borekb
Copy link

borekb commented Jul 27, 2020

Tips for getting it work

I've successfully set up @sotnikov/next-with-apollo (this PR) + Apollo Client 3.0 + Next.js 9.4, here are a few notes.

This is my pages/_app.tsx:

import React from 'react';
import { ApolloProvider, ApolloClient } from '@apollo/client';
import NextApp, { AppProps } from 'next/app';
import { getApolloClient } from '../utils/apolloClient';
import withApollo from '@sotnikov/next-with-apollo';
import { getDataFromTree } from '@apollo/client/react/ssr';

type Props = AppProps & {
  apollo: ApolloClient<{}>;
};

const App = ({ Component, pageProps, apollo }: Props) => (
  <ApolloProvider client={apollo}>
    <Component {...pageProps} />
  </ApolloProvider>
);

App.getInitialProps = async (appContext: any) => {
  const appProps = await NextApp.getInitialProps(appContext);
  return { ...appProps };
};

export default withApollo(getApolloClient, { getDataFromTree })(App);

❗️ getDataFromTree must be imported from @apollo/client/react/ssr, not @apollo/react-ssr. I initially forgot that and was getting this error:

GraphQL error occurred [getDataFromTree] Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

Importing from @apollo/client/react/ssr made the problem go away.

Another gotcha was getting "Loading..." instead of the rendered output from pages/index.js:

import React from "react";
import { gql, useQuery } from "@apollo/client";

const PRODUCTS_QUERY = gql`
  query {
    products {
      id
      name
    }
  }
`;

const IndexPage = () => {
  const { loading, error, data } = useQuery(PRODUCTS_QUERY);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {JSON.stringify(error)}</p>;

  return (
    <ul>
      {data.products.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
};

export default IndexPage;

The problem here is the condition if (loading) – it should be if (!data && loading), see apollographql/apollo-client#6422 (comment).

With that, I got properly rendered SSR page.

@ilackovic
Copy link

Hi @borekb ,

I'm really interested what you did in "import { getApolloClient } from '../utils/apolloClient';"
Could you post that file so I can take a look what I'm doing wrong?

I'm stuck and can't get withApollo to work, I'm stuck with Invalid hook call even tho I tried to reproduce what you mentioned in your post.

I would really appreciate it !

This is my version of apolloClient:

import { ApolloClient, ApolloLink, HttpLink, InMemoryCache } from '@apollo/client';
import { useMemo } from 'react';

let apolloClient;
const firstApiGraphqlURL = process.env.FIRST_URL;
const secondApiGraphqlURL = process.env.SECOND_URL;

function createApolloClient(headers) {
  const firstApiLink = new HttpLink({
    credentials: 'include',
    uri: firstApiGraphqlURL,
    headers: {
      cookie: headers?.cookie,
    },
  });

  const secondApiLink = new HttpLink({
    uri: `${secondApiGraphqlURL}/graphql`,
  });
  return new ApolloClient({
    ssrMode: typeof window === 'undefined',
    link: ApolloLink.from([
      // eslint-disable-next-line no-unused-vars
      ApolloLink.split(
        operation => operation.getContext().clientName === 'secondApiLink',
        firstApiLink,
        secondApiLink,
      ),
    ]),
    cache: new InMemoryCache(),
  });
}

export function initializeApollo(initialState = null) {
  const _apolloClient = apolloClient ?? createApolloClient();

  if (initialState) {
    _apolloClient.cache.restore(initialState);
  }
  if (typeof window === 'undefined') return _apolloClient;
  if (!apolloClient) apolloClient = _apolloClient;

  return _apolloClient;
}

export function useApollo(initialState) {
  const store = useMemo(() => initializeApollo(initialState), [initialState]);
  return store;
}

Error that I get is :

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

> 56 |   const store = useMemo(() => initializeApollo(initialState), [initialState]);
     |                        ^
  57 |   return store;
  58 | }

@borekb
Copy link

borekb commented Aug 3, 2020

@ilackovic Actually we've moved utils/apolloClient to _app.tsx since it's so short, the entire file now looks like this:

import React from 'react';
import { ApolloProvider, ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import NextApp, { AppProps } from 'next/app';
import withApollo, { InitApolloOptions } from '@sotnikov/next-with-apollo';
import { getDataFromTree } from '@apollo/client/react/ssr';

type Props = AppProps & {
  apollo: ApolloClient<{}>;
};

const App = ({ Component, pageProps, apollo }: Props) => (
  <ApolloProvider client={apollo}>
    <Component {...pageProps} />
  </ApolloProvider>
);

App.getInitialProps = async (appContext: any) => {
  const appProps = await NextApp.getInitialProps(appContext);
  return { ...appProps };
};

export default withApollo(
  ({ initialState }: InitApolloOptions<any>): ApolloClient<any> => {
    return new ApolloClient({
      link: new HttpLink({
        uri: 'http://localhost:3000/api/graphql',
      }),
      cache: new InMemoryCache().restore(initialState || {}),
    });
  },
  { getDataFromTree }
)(App);

@ilackovic
Copy link

ilackovic commented Aug 3, 2020

@ilackovic Actually we've moved utils/apolloClient to _app.tsx since it's so short, the entire file now looks like this:

import React from 'react';
import { ApolloProvider, ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import NextApp, { AppProps } from 'next/app';
import withApollo, { InitApolloOptions } from '@sotnikov/next-with-apollo';
import { getDataFromTree } from '@apollo/client/react/ssr';

type Props = AppProps & {
  apollo: ApolloClient<{}>;
};

const App = ({ Component, pageProps, apollo }: Props) => (
  <ApolloProvider client={apollo}>
    <Component {...pageProps} />
  </ApolloProvider>
);

App.getInitialProps = async (appContext: any) => {
  const appProps = await NextApp.getInitialProps(appContext);
  return { ...appProps };
};

export default withApollo(
  ({ initialState }: InitApolloOptions<any>): ApolloClient<any> => {
    return new ApolloClient({
      link: new HttpLink({
        uri: 'http://localhost:3000/api/graphql',
      }),
      cache: new InMemoryCache().restore(initialState || {}),
    });
  },
  { getDataFromTree }
)(App);

@borekb ,

Thank you for answering !

Sadly, this doesn't work for me, I just can't get SSR to work.

I even created a new Next project and tried to reproduce your code exactly as you posted it, but no luck.
My page always renders "Loading" div first (in source code) and initialState from _app.tsx is undefined in all cases.

Since I literally went ahead and copy pasted anything, I really don't understand what I'm missing if it works for you...
Any thoughts?

I saw a lot of posts in the last few months where people have issues with SSR and Apollo...
It's a shame SSR documentation for this case is practically non-existent or out of date... Next team is pushing people on SSG without the regard of why some of us picked this framework in the first place (SSR obviously)..

@sotnikov-link
Copy link
Author

I use SSR and it works.

@ilackovic
Copy link

I use SSR and it works.

You are right.

I tweaked the example repo I created and I managed to make it work there.

But, with the same configuration, same query, same package.json, it doesn't work in my main project...

My only conclusion here is that something in my project is blocking SSR somehow.

Thank you for your help !

@Uniiq
Copy link

Uniiq commented Aug 6, 2020

Any plans to merge this PR?

@lfades
Copy link
Owner

lfades commented Aug 7, 2020

@Uniiq The PR still has some conflicts and it hasn't been updated since April. Personally I haven't had the time to try out Apollo 3 but I'll be happy to review a PR that adds support for it.

@dyyyl
Copy link

dyyyl commented Oct 2, 2020

@lfades is there any movement on this? The PR looks really good, and doesn't seem to conflict with the main branch.

@lfades
Copy link
Owner

lfades commented Oct 2, 2020

@dyyyl Yeah looks like it was updated. I'll try to review it soon and get it merged. In the meantime please remember that the package itself only uses Apollo to get TS types and for tests, so getting this PR merged doesn't change any actual functionality. The package should already work with the latest Apollo version.

@webdeb
Copy link

webdeb commented Oct 22, 2020

In the meantime please remember that the package itself only uses Apollo to get TS types and for tests, so getting this PR merged doesn't change any actual functionality. The package should already work with the latest Apollo version.

@lfades this is the error I get, after cleaning up the package.json and only using "@apollo/client" with yours next-with-apollo

Bildschirmfoto 2020-10-22 um 11 22 39

It's dev mode, so I am assuming that you expect apollo-client being installed as peerDependencies. However like I said, when you clean up the obsolete packages you'll face these errors.

Update: "@sotnikov/next-with-apollo" works 🎉

@marvinroeben
Copy link

works also for me on "@apollo/client": "^3.2.9" - any plans on merging that soon?

@younes200
Copy link

younes200 commented Dec 5, 2020

@lfades please merge, this will be helpful !

@dvakatsiienko
Copy link

There there is a package.jsonconflicting file only — could be resolved and merged?

Comment on lines +53 to +55
"@apollo/client": "^3.0.0-beta.43",
"@apollo/react-components": "^4.0.0-beta.1",
"@apollo/react-ssr": "^4.0.0-beta.1",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we switch to the latest versions?

package.json Outdated Show resolved Hide resolved
@switz
Copy link

switz commented Dec 8, 2021

Any plans to merge this? Seems to be breaking for me w/r/t this issue: apollographql/apollo-client#9122

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

Successfully merging this pull request may close these issues.