next-with-apollo icon indicating copy to clipboard operation
next-with-apollo copied to clipboard

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

Open sotnikov-link opened this issue 4 years ago • 21 comments

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 avatar Apr 21 '20 13:04 sotnikov-link

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.

sotnikov-link avatar Apr 22 '20 11:04 sotnikov-link

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

mvantoorn avatar Jun 17 '20 15:06 mvantoorn

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

sotnikov-link avatar Jun 17 '20 15:06 sotnikov-link

What's the status of this pull request?

shihfu avatar Jul 26 '20 00:07 shihfu

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?

chemicalkosek avatar Jul 26 '20 05:07 chemicalkosek

@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 avatar Jul 27 '20 21:07 borekb

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 https://github.com/apollographql/apollo-client/issues/6422#issuecomment-650070761.

With that, I got properly rendered SSR page.

borekb avatar Jul 27 '20 21:07 borekb

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 | }

ilackovic avatar Aug 03 '20 15:08 ilackovic

@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 avatar Aug 03 '20 15:08 borekb

@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)..

ilackovic avatar Aug 03 '20 17:08 ilackovic

I use SSR and it works.

sotnikov-link avatar Aug 03 '20 17:08 sotnikov-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 !

ilackovic avatar Aug 03 '20 18:08 ilackovic

Any plans to merge this PR?

Uniiq avatar Aug 06 '20 11:08 Uniiq

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

lfades avatar Aug 07 '20 00:08 lfades

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

dyyyl avatar Oct 02 '20 11:10 dyyyl

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

lfades avatar Oct 02 '20 14:10 lfades

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 🎉

webdeb avatar Oct 22 '20 09:10 webdeb

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

marvinroeben avatar Dec 01 '20 17:12 marvinroeben

@lfades please merge, this will be helpful !

younes200 avatar Dec 05 '20 14:12 younes200

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

dvakatsiienko avatar Feb 19 '21 12:02 dvakatsiienko

Any plans to merge this? Seems to be breaking for me w/r/t this issue: https://github.com/apollographql/apollo-client/issues/9122

switz avatar Dec 08 '21 06:12 switz