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