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 useAuthStore from '../stores/useAuthStore';
8
9const {STRAPI_URL = ''} = process.env;
10
11// https://github.com/vercel/next.js/tree/canary/examples/with-apollo
12// https://github.com/vercel/next.js/tree/canary/examples/layout-component
13// https://www.apollographql.com/docs/react/networking/authentication/
14// https://www.apollographql.com/docs/react/data/error-handling/
15// https://www.apollographql.com/docs/react/caching/cache-field-behavior/#the-merge-function
16
17export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__';
18let apolloClient;
19
20const authLink = setContext((_, {headers}) => {
21 // get the authentication token from local storage if it exists
22 const {token} = useAuthStore.getState();
23 // return the headers to the context so httpLink can read them
24 return {
25 headers: {
26 ...headers,
27 authorization: token ? `Bearer ${token}` : '',
28 },
29 };
30});
31
32const errorLink = onError(({graphQLErrors = []}) => {
33 const [error] = graphQLErrors;
34 console.error({graphQLErrors});
35
36 if (
37 error?.message === 'Invalid token.' ||
38 error?.message === 'User Not Found' ||
39 error?.message === 'Your account has been blocked by the administrator.'
40 ) {
41 useAuthStore.getState().setToken();
42 useAuthStore.getState().setUser();
43 localStorage.removeItem('token');
44 localStorage.removeItem('user');
45 }
46});
47
48const httpLink = uri =>
49 new HttpLink({
50 uri,
51 credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
52 });
53
54const createApolloClient = () => {
55 return new ApolloClient({
56 ssrMode: typeof window === 'undefined',
57 link: from([authLink, errorLink, httpLink(`${STRAPI_URL}/graphql`)]),
58 cache: new InMemoryCache({
59 typePolicies: {
60 Event: {
61 fields: {
62 waitingList: {
63 merge(_, incoming) {
64 return incoming;
65 },
66 },
67 },
68 },
69 Car: {
70 fields: {
71 passengers: {
72 merge(_, incoming) {
73 return incoming;
74 },
75 },
76 },
77 },
78 },
79 }),
80 });
81};
82
83export const initializeApollo = (initialState = null) => {
84 const _apolloClient = apolloClient ?? createApolloClient();
85
86 // If your page has Next.js data fetching methods that use Apollo Client, the initial state gets hydrated here
87 if (initialState) {
88 // Get existing cache, loaded during client side data fetching
89 const existingCache = _apolloClient.extract();
90 // Merge the existing cache into data passed from getStaticProps/getServerSideProps
91 const data = merge(initialState, existingCache, {
92 // Combine arrays using object equality (like in sets)
93 arrayMerge: (destinationArray, sourceArray) => [
94 ...sourceArray,
95 ...destinationArray.filter(d => sourceArray.every(s => !isEqual(d, s))),
96 ],
97 });
98 // Restore the cache with the merged data
99 _apolloClient.cache.restore(data);
100 }
101
102 // For SSG and SSR always create a new Apollo Client
103 if (typeof window === 'undefined') return _apolloClient;
104
105 // Create the Apollo Client once in the client
106 if (!apolloClient) apolloClient = _apolloClient;
107 return _apolloClient;
108};
109
110export const addApolloState = (client, pageProps) => {
111 if (pageProps?.props) {
112 pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
113 }
114 return pageProps;
115};
116
117export const useApollo = pageProps => {
118 const state = pageProps[APOLLO_STATE_PROP_NAME];
119
120 return useMemo(() => initializeApollo(state), [state]);
121};