frontend/lib/apolloClient.ts (view raw)
1import {useMemo} from 'react';
2import {ApolloClient, HttpLink, InMemoryCache, from} from '@apollo/client';
3import {setContext} from '@apollo/client/link/context';
4import {onError} from '@apollo/client/link/error';
5import merge from 'deepmerge';
6import isEqual from 'lodash/isEqual';
7import {signOut, useSession} from 'next-auth/react';
8
9export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__';
10let apolloClient: ApolloClient<any>;
11
12const authLink = (jwt: string | null) =>
13 setContext(async (_, {headers}) => {
14 // return the headers to the context so httpLink can read them
15 return {
16 headers: {
17 ...headers,
18 authorization: jwt ? `Bearer ${jwt}` : '',
19 },
20 };
21 });
22
23const errorLink = onError(({operation, networkError, response}) => {
24 console.error({networkError, operation});
25 if (response) console.error(JSON.stringify(response, null, 4));
26 const responseStatus = networkError?.response?.status;
27
28 if (responseStatus === 401)
29 signOut({
30 callbackUrl: '/auth/login',
31 });
32});
33
34const httpLink = (uri: string) =>
35 new HttpLink({
36 uri, // Server URL (must be absolute)
37 credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
38 });
39
40const createApolloClient = (uri: string, jwt: string | null) => {
41 return new ApolloClient({
42 ssrMode: typeof window === 'undefined',
43 link: from([authLink(jwt), errorLink, httpLink(uri)]),
44 cache: new InMemoryCache(),
45 });
46};
47
48export const initializeApollo = (
49 uri: string,
50 jwt: string | null,
51 initialState = null
52) => {
53 const _apolloClient = apolloClient ?? createApolloClient(uri, jwt);
54
55 // If your page has Next.js data fetching methods that use Apollo Client, the initial state gets hydrated here
56 if (initialState) {
57 // Get existing cache, loaded during client side data fetching
58 const existingCache = _apolloClient.extract();
59 // Merge the existing cache into data passed from getStaticProps/getServerSideProps
60 const data = merge(initialState, existingCache, {
61 // Combine arrays using object equality (like in sets)
62 arrayMerge: (destinationArray, sourceArray) => [
63 ...sourceArray,
64 ...destinationArray.filter(d => sourceArray.every(s => !isEqual(d, s))),
65 ],
66 });
67 // Restore the cache with the merged data
68 _apolloClient.cache.restore(data);
69 }
70
71 // For SSG and SSR always create a new Apollo Client
72 if (typeof window === 'undefined') return _apolloClient;
73
74 // Create the Apollo Client once in the client
75 if (!apolloClient) apolloClient = _apolloClient;
76 return _apolloClient;
77};
78
79export const addApolloState = (client: ApolloClient<any>, pageProps: any) => {
80 if (pageProps?.props) {
81 pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
82 }
83 return pageProps;
84};
85
86export const useApollo = (pageProps: any) => {
87 const state = pageProps[APOLLO_STATE_PROP_NAME];
88 const {data: session} = useSession();
89 return useMemo(
90 () => initializeApollo('/graphql/', session?.token?.jwt, state),
91 [state, session]
92 );
93};