Would you have a Typescript version of the with-apollo
example?
Thanks!
General curiosity, why did you ignore the issue template.
Hi @timneutkens sorry I didn't find one that would match this kinda of question, I was going to choose the Feature Request but this is not a feature whatsoever.
I apologize.
This would be great! I have converted lib/apollo.js
in the past but always get stuck on the error where <AppTree pageProps={{...pageProps, apolloClient}} />
complains about apolloClient not being a valid prop of AppTree. I really hate having to @ts-ignore
that line!
Additionally, there are a few parts of the existing JavaScript code where TypeScript exposes some issues with function arguments. If I have time, I will put up a CodeSandbox with what I was able to accomplish previously. It's like 99% of the way there.
@msheakoski That would be great, thanks!
@samuelcastro I did some workaround about this, you can try to find some inspiration in my template (newest versions on refactor
branch (still working on), and previous one (which used to wrap _app.tsx
) is on master
branch.
Thanks @borisowsky it looks great.
Here's what I'm using copied as is from one of my projects:
// https://github.com/zeit/next.js/tree/ff2d28c4ff0a2cc1ce408817f2714eebf1cf72c2/examples/with-apollo
import React from 'react';
import Head from 'next/head';
import { ApolloProvider } from '@apollo/react-hooks';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache, NormalizedCacheObject } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { createPersistedQueryLink } from "apollo-link-persisted-queries";
import { fetch } from './fetch-polyfill';
import { NextPage } from 'next';
let apolloClient: ApolloClient<NormalizedCacheObject> | null = null;
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
*/
export function withApollo<PageProps>(PageComponent: NextPage<PageProps>, { ssr = true } = {}) {
type ApolloPageProps = PageProps & { apolloClient?: ApolloClient<NormalizedCacheObject> | null; apolloState?: NormalizedCacheObject; }
const WithApollo: NextPage<ApolloPageProps> = ({ apolloClient, apolloState, ...pageProps }) => {
const client = apolloClient || initApolloClient(apolloState);
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps as any as PageProps} />
</ApolloProvider>
);
};
// Set the correct displayName in development
if (process.env.NODE_ENV !== 'production') {
const displayName = PageComponent.displayName || PageComponent.name || 'Component';
if (displayName === 'App') {
console.warn('This withApollo HOC only works with PageComponents.');
}
WithApollo.displayName = `withApollo(${displayName})`;
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async (ctx) => {
const { AppTree } = ctx;
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const cookie = ctx && ctx.req && ctx.req.headers && ctx.req.headers.cookie && String(ctx.req.headers.cookie);
const apolloClient = (ctx.apolloClient = initApolloClient({}, cookie));
// Run wrapped getInitialProps methods
let pageProps = {} as PageProps;
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx);
}
// Only on the server:
if (typeof window === 'undefined') {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps;
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import('@apollo/react-ssr');
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient,
}}
/>,
);
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error('Error while running `getDataFromTree`', error);
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind();
}
}
// Extract query data from the Apollo store
const apolloState = apolloClient.cache.extract();
return {
...pageProps,
apolloState,
};
};
}
return WithApollo;
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
*/
function initApolloClient(initialState?: NormalizedCacheObject, cookie?: string) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return createApolloClient(initialState, cookie);
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(initialState);
}
return apolloClient;
}
/**
* Creates and configures the ApolloClient
*/
function createApolloClient(initialState: NormalizedCacheObject = {}, cookie?: string): ApolloClient<NormalizedCacheObject> {
const headers = cookie ? {cookie}: undefined;
const link = createPersistedQueryLink().concat(new HttpLink({
uri: process.env.API_URL + '/graphql', // Server URL (must be absolute)
credentials: 'include', // Additional fetch() options like `credentials` or `headers`
headers,
fetch,
useGETForQueries: true,
}));
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
return new ApolloClient({
ssrMode: typeof window === 'undefined', // Disables forceFetch on the server (so queries are only run once)
link,
cache: new InMemoryCache().restore(initialState),
connectToDevTools: true
});
}
Is anyone having issues with the example? i'm using it with typescript, similar to above and the data isnt being server rendered
@JClackett Sorry I thought I replied a couple of weeks ago when I last had a look at this. I too am having issues with this TypeScript Apollo example.
Using @mmiszy 's example was a great start but doesn't seem to work for me. Specifically when I tried to type withApollo<PageProps>
.
Here's what I have so far with the current canary build of with-apollo project.
import React, { ReactNode } from 'react';
import App from 'next/app';
import { ApolloProvider } from '@apollo/react-hooks';
import createApolloClient from './createApolloClient';
import { NextPageContext, NextPage } from 'next';
// import { AppContext } from 'next/app';
import { ApolloClient, NormalizedCacheObject } from 'apollo-boost';
// On the client, we store the Apollo Client in the following variable.
// This prevents the client from reinitializing between page transitions.
let globalApolloClient: ApolloClient<NormalizedCacheObject> | null = null;
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* @param {NormalizedCacheObject} initialState
* @param {NextPageContext} ctx
*/
const initApolloClient = (
initialState: NormalizedCacheObject,
ctx?: NextPageContext,
): ApolloClient<NormalizedCacheObject> => {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return createApolloClient(initialState, ctx);
}
// Reuse client on the client-side
if (!globalApolloClient) {
globalApolloClient = createApolloClient(initialState, ctx);
}
return globalApolloClient;
};
/**
* Installs the Apollo Client on NextPageContext
* or NextAppContext. Useful if you want to use apolloClient
* inside getStaticProps, getStaticPaths or getServerProps
* @param {NextPageContext | NextAppContext} ctx
*/
export const initOnContext = (ctx: NextPageContext): NextPageContext => {
const inAppContext = Boolean(ctx.ctx);
// We consider installing `withApollo({ ssr: true })` on global App level
// as antipattern since it disables project wide Automatic Static Optimization.
if (process.env.NODE_ENV === 'development') {
if (inAppContext) {
console.warn(
'Warning: You have opted-out of Automatic Static Optimization due to `withApollo` in `pages/_app`.\n' +
'Read more: https://err.sh/next.js/opt-out-auto-static-optimization\n',
);
}
}
// Initialize ApolloClient if not already done
const apolloClient = ctx.apolloClient || initApolloClient(ctx.apolloState || {}, inAppContext ? ctx.ctx : ctx);
// We send the Apollo Client as a prop to the component to avoid calling initApollo() twice in the server.
// Otherwise, the component would have to call initApollo() again but this
// time without the context. Once that happens, the following code will make sure we send
// the prop as `null` to the browser.
apolloClient.toJSON = (): null => null;
// Add apolloClient to NextPageContext & NextAppContext.
// This allows us to consume the apolloClient inside our
// custom `getInitialProps({ apolloClient })`.
ctx.apolloClient = apolloClient;
if (inAppContext) {
ctx.ctx.apolloClient = apolloClient;
}
return ctx;
};
/**
* Creates a withApollo HOC
* that provides the apolloContext
* to a next.js Page or AppTree.
* @param {Object} withApolloOptions
* @param {Boolean} [withApolloOptions.ssr=false]
* @returns {(PageComponent: ReactNode) => ReactNode}
*/
export function withApollo({ ssr = false } = {}) {
return function(PageComponent: NextPage): ReactNode {
function WithApollo({
apolloClient,
apolloState,
...pageProps
}: {
apolloClient: ApolloClient<NormalizedCacheObject>;
apolloState: NormalizedCacheObject;
}): ReactNode {
let client;
if (apolloClient) {
// Happens on: getDataFromTree & next.js ssr
client = apolloClient;
} else {
// Happens on: next.js csr
client = initApolloClient(apolloState, undefined);
}
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
}
// Set the correct displayName in development
if (process.env.NODE_ENV !== 'production') {
const displayName = PageComponent.displayName || PageComponent.name || 'Component';
WithApollo.displayName = `withApollo(${displayName})`;
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async (ctx: NextPageContext): Promise<object> => {
const inAppContext = Boolean(ctx.ctx);
const { apolloClient } = initOnContext(ctx);
// Run wrapped getInitialProps methods
let pageProps = {};
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx);
} else if (inAppContext) {
pageProps = await App.getInitialProps(ctx);
}
// Only on the server:
if (typeof window === 'undefined') {
const { AppTree } = ctx;
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps;
}
// Only if dataFromTree is enabled
if (ssr && AppTree) {
try {
// Import `@apollo/react-ssr` dynamically.
// We don't want to have this in our client bundle.
const { getDataFromTree } = await import('@apollo/react-ssr');
// Since AppComponents and PageComponents have different context types
// we need to modify their props a little.
let props;
if (inAppContext) {
props = { ...pageProps, apolloClient };
} else {
props = { pageProps: { ...pageProps, apolloClient } };
}
// Take the Next.js AppTree, determine which queries are needed to render,
// and fetch them. This method can be pretty slow since it renders
// your entire AppTree once for every query. Check out apollo fragments
// if you want to reduce the number of rerenders.
// https://www.apollographql.com/docs/react/data/fragments/
await getDataFromTree(<AppTree {...props} />);
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error('Error while running `getDataFromTree`', error);
}
}
}
return {
...pageProps,
// Extract query data from the Apollo store
apolloState: apolloClient.cache.extract(),
// Provide the client for ssr. As soon as this payload
// gets JSON.stringified it will remove itself.
apolloClient: ctx.apolloClient,
};
};
}
return WithApollo;
};
}
My outstanding issues are:
Property 'ctx' does not exist on type 'NextPageContext'.
Property 'apolloClient' does not exist on type 'NextPageContext'.
Property 'apolloState' does not exist on type 'NextPageContext'.
Type '{ apolloClient: any; } | { pageProps: { apolloClient: any; }; }' is not assignable to type 'IntrinsicAttributes & AppInitialProps & { [name: string]: any; } & { children?: ReactNode; }'.
Property 'pageProps' is missing in type '{ apolloClient: any; }' but required in type 'AppInitialProps'.
for 'AppTree'I am not an expert on TypeScript so maybe these will make more sense to someone more experienced.
I see someone has added types for 'initOnContext' function but I can't find a reference for the NextAppContext type. Using 'AppContext' from 'next/app' was unsuccessful.
Any help would e greatly appreciated. Thanks
This is great thanks for sharing @jacknevitt! Maybe you should create a PR, I think this is a really good start.
I modify it a little bit to include the missing types which are:
interface NextPageContextWithApollo extends NextPageContext {
apolloClient: ApolloClient<NormalizedCacheObject> | null;
apolloState: NormalizedCacheObject;
ctx: NextPageContextApp;
}
type NextPageContextApp = NextPageContextWithApollo & AppContext;
This is the complete code:
import React, { ReactNode } from 'react';
import App, { AppContext } from 'next/app';
import Head from 'next/head';
import { ApolloProvider } from '@apollo/react-hooks';
import { ApolloClient } from 'apollo-client';
import { NormalizedCacheObject } from 'apollo-cache-inmemory';
import { NextPageContext, NextPage } from 'next';
import createApolloClient from './apolloClient';
interface NextPageContextWithApollo extends NextPageContext {
apolloClient: ApolloClient<NormalizedCacheObject> | null;
apolloState: NormalizedCacheObject;
ctx: NextPageContextApp;
}
type NextPageContextApp = NextPageContextWithApollo & AppContext;
// On the client, we store the Apollo Client in the following variable.
// This prevents the client from reinitializing between page transitions.
let globalApolloClient: ApolloClient<NormalizedCacheObject> | null = null;
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* @param {NormalizedCacheObject} initialState
* @param {NextPageContext} ctx
*/
const initApolloClient = (initialState: NormalizedCacheObject, ctx?: NextPageContext) => {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return createApolloClient(initialState, ctx);
}
// Reuse client on the client-side
if (!globalApolloClient) {
globalApolloClient = createApolloClient(initialState, ctx);
}
return globalApolloClient;
};
/**
* Installs the Apollo Client on NextPageContext
* or NextAppContext. Useful if you want to use apolloClient
* inside getStaticProps, getStaticPaths or getServerSideProps
* @param {NextPageContext | NextAppContext} ctx
*/
export const initOnContext = (ctx: NextPageContextApp): NextPageContextApp => {
const inAppContext = Boolean(ctx.ctx);
// We consider installing `withApollo({ ssr: true })` on global App level
// as antipattern since it disables project wide Automatic Static Optimization.
if (process.env.NODE_ENV === 'development') {
if (inAppContext) {
console.warn(
'Warning: You have opted-out of Automatic Static Optimization due to `withApollo` in `pages/_app`.\n' +
'Read more: https://err.sh/next.js/opt-out-auto-static-optimization\n',
);
}
}
// Initialize ApolloClient if not already done
// TODO: Add proper types here:
// https://github.com/zeit/next.js/issues/9542
const apolloClient = ctx.apolloClient || initApolloClient(ctx.apolloState || {}, inAppContext ? ctx.ctx : ctx);
// We send the Apollo Client as a prop to the component to avoid calling initApollo() twice in the server.
// Otherwise, the component would have to call initApollo() again but this
// time without the context. Once that happens, the following code will make sure we send
// the prop as `null` to the browser.
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
apolloClient.toJSON = () => null;
// Add apolloClient to NextPageContext & NextAppContext.
// This allows us to consume the apolloClient inside our
// custom `getInitialProps({ apolloClient })`.
ctx.apolloClient = apolloClient;
if (inAppContext) {
ctx.ctx.apolloClient = apolloClient;
}
return ctx;
};
/**
* Creates a withApollo HOC
* that provides the apolloContext
* to a next.js Page or AppTree.
* @param {Object} withApolloOptions
* @param {Boolean} [withApolloOptions.ssr=false]
* @returns {(PageComponent: ReactNode) => ReactNode}
*/
export const withApollo = ({ ssr = false } = {}) => (PageComponent: NextPage): ReactNode => {
const WithApollo = ({
apolloClient,
apolloState,
...pageProps
}: {
apolloClient: ApolloClient<NormalizedCacheObject>;
apolloState: NormalizedCacheObject;
}): ReactNode => {
let client;
if (apolloClient) {
// Happens on: getDataFromTree & next.js ssr
client = apolloClient;
} else {
// Happens on: next.js csr
client = initApolloClient(apolloState, undefined);
}
return (
<ApolloProvider client={client}>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
// Set the correct displayName in development
if (process.env.NODE_ENV !== 'production') {
const displayName = PageComponent.displayName || PageComponent.name || 'Component';
WithApollo.displayName = `withApollo(${displayName})`;
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async (ctx: NextPageContextApp): Promise<object> => {
const inAppContext = Boolean(ctx.ctx);
const { apolloClient } = initOnContext(ctx);
// Run wrapped getInitialProps methods
let pageProps = {};
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx);
} else if (inAppContext) {
pageProps = await App.getInitialProps(ctx);
}
// Only on the server:
if (typeof window === 'undefined') {
const { AppTree } = ctx;
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps;
}
// Only if dataFromTree is enabled
if (ssr && AppTree) {
try {
// Import `@apollo/react-ssr` dynamically.
// We don't want to have this in our client bundle.
const { getDataFromTree } = await import('@apollo/react-ssr');
// Since AppComponents and PageComponents have different context types
// we need to modify their props a little.
let props: any;
if (inAppContext) {
props = { ...pageProps, apolloClient };
} else {
props = { pageProps: { ...pageProps, apolloClient } };
}
// Take the Next.js AppTree, determine which queries are needed to render,
// and fetch them. This method can be pretty slow since it renders
// your entire AppTree once for every query. Check out apollo fragments
// if you want to reduce the number of rerenders.
// https://www.apollographql.com/docs/react/data/fragments/
// eslint-disable-next-line react/jsx-props-no-spreading
await getDataFromTree(<AppTree {...props} />);
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error('Error while running `getDataFromTree`', error);
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind();
}
}
return {
...pageProps,
// Extract query data from the Apollo store
apolloState: apolloClient?.cache.extract(),
// Provide the client for ssr. As soon as this payload
apolloClient: ctx.apolloClient,
};
};
}
return WithApollo;
};
Thanks for this @samuelcastro .
I've noticed you used // @ts-ignore
above ApolloClient.toJSON()
. Is it possible to type this? My setup prevents me from using ts-ignore as it suppresses compilation errors.
Also you have declared props for AppTree as any
which is also not ideal.
Yeah I know, but that's all I could make, those are the 2 missing pieces I don't know yet how to type.
Thanks @jacknevitt , hope someone can help with the apolloClient.toJSON
part. I still have problem with it.
Thanks @jacknevitt and @samuelcastro , btw i got error for using withApollo hoc in custom app _app.tsx
const MyApp: ({ Component, pageProps }: AppPropsType<Router, {}>) => JSX.Element
Argument of type '({ Component, pageProps }: AppPropsType<Router, {}>) => JSX.Element' is not assignable to parameter of type 'NextComponentType<NextPageContext, {}, {}>'.
Type '({ Component, pageProps }: AppPropsType<Router, {}>) => JSX.Element' is not assignable to type 'FunctionComponent<{}> & { getInitialProps?(context: NextPageContext): {} | Promise<{}>; }'.
Type '({ Component, pageProps }: AppPropsType<Router, {}>) => JSX.Element' is not assignable to type 'FunctionComponent<{}>'.
Types of parameters '__0' and 'props' are incompatible.
Type '{ children?: ReactNode; }' is not assignable to type 'AppPropsType<Router, {}>'.
Property 'pageProps' is missing in type '{ children?: ReactNode; }' but required in type 'AppInitialProps'.ts(2345)
utils.d.ts(102, 5): 'pageProps' is declared here.
@timshingyu Please check this issue, especially this comment: https://github.com/zeit/next.js/issues/10453#issuecomment-583555371
There were some break changes, please make sure you change the way to use withApollo
I used recompose
so it will look like this:
export default compose(
withApollo({ ssr: true }),
withAuth(),
)(MyComponent);
@sangdth Thx, actually already using new syntax. Still got the type error.
Hi, I do not understand how I can access the client in getStaticPaths
.
@mstn did you find a solution?
Hello, a solution I may use for the .toJSON
type to avoid disable ts or use any
should look like this :
const withToJSONApolloClient =
apolloClient as ApolloClient<NormalizedCacheObject> & { toJSON: () => null }
withToJSONApolloClient.toJSON = () => null
Any idea how I can improve this ? 馃槂
You can use the same initOnContext
function to access the client in GSSP/GSP.
What I am noticing is that there is no ctx.res
unless it is a page component using this HOC in reference to this check. I'm using it with the App and there ctx.ctx.res
exists. Perhaps this should check for whether or not inAppContext
exists?
I tried @samuelcastro's solution but I keep getting this error Module not found: Can't resolve 'aws-sdk' in '[...]/node_modules/node-pre-gyp/lib'
. I'm guessing some server-side dependencies somehow leak to the browser. It also complained that I cannot use getServerSideProps
in conjunction with getInitialProps
. I'm guessing this has to do with wrapping the getInitialProps methods after the if (ssr || PageComponent.getInitialProps)
condition. Does anyone have an up-to-date fully working example? 馃檹
I'm using this implementation https://github.com/UnlyEd/next-right-now/blob/v1-hyb-mst-aptd-gcms-lcz-sty/src/hocs/withApollo.tsx and it works fine with SSG/SSR.
Demo at https://nrn-v1-hyb-mst-aptd-gcms-lcz-sty-c1-peyfrlyfe.now.sh/fr
Related: https://nrn-v1-hyb-mst-aptd-gcms-lcz-sty-c1-peyfrlyfe.now.sh/fr/examples/built-in-features/graphql
Learn more about NRN: https://unlyed.github.io/next-right-now/
import * as React from 'react';
import { withApollo, WithApolloClient } from 'react-apollo';
class AComponent extends React.Component<WithApolloClient<{}>> {
render() {
// ...
}
}
export default withApollo(AComponent);
Most helpful comment
I modify it a little bit to include the missing types which are:
This is the complete code: