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(error => {
33 const {networkError, graphQLErrors} = error;
34 console.error({networkError, graphQLErrors});
35 const isUnauthorized = networkError?.response?.status === 401;
36
37 if (isUnauthorized) {
38 console.error('Unauthorized response received from GraphQL. Logout user.');
39 useAuthStore.getState().setToken();
40 useAuthStore.getState().setUser();
41 localStorage.removeItem('token');
42 localStorage.removeItem('user');
43 }
44});
45
46const httpLink = uri =>
47 new HttpLink({
48 uri,
49 credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
50 });
51
52const createApolloClient = () => {
53 return new ApolloClient({
54 ssrMode: typeof window === 'undefined',
55 link: from([authLink, errorLink, httpLink(`${STRAPI_URL}/graphql`)]),
56 cache: new InMemoryCache({
57 typePolicies: {
58 Event: {
59 fields: {
60 waitingList: {
61 merge(_, incoming) {
62 return incoming;
63 },
64 },
65 },
66 },
67 Car: {
68 fields: {
69 passengers: {
70 merge(_, incoming) {
71 return incoming;
72 },
73 },
74 },
75 },
76 },
77 }),
78 });
79};
80
81export const initializeApollo = (initialState = null) => {
82 const _apolloClient = apolloClient ?? createApolloClient();
83
84 // If your page has Next.js data fetching methods that use Apollo Client, the initial state gets hydrated here
85 if (initialState) {
86 // Get existing cache, loaded during client side data fetching
87 const existingCache = _apolloClient.extract();
88 // Merge the existing cache into data passed from getStaticProps/getServerSideProps
89 const data = merge(initialState, existingCache, {
90 // Combine arrays using object equality (like in sets)
91 arrayMerge: (destinationArray, sourceArray) => [
92 ...sourceArray,
93 ...destinationArray.filter(d => sourceArray.every(s => !isEqual(d, s))),
94 ],
95 });
96 // Restore the cache with the merged data
97 _apolloClient.cache.restore(data);
98 }
99
100 // For SSG and SSR always create a new Apollo Client
101 if (typeof window === 'undefined') return _apolloClient;
102
103 // Create the Apollo Client once in the client
104 if (!apolloClient) apolloClient = _apolloClient;
105 return _apolloClient;
106};
107
108export const addApolloState = (client, pageProps) => {
109 if (pageProps?.props) {
110 pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
111 }
112 return pageProps;
113};
114
115export const useApollo = pageProps => {
116 const state = pageProps[APOLLO_STATE_PROP_NAME];
117
118 return useMemo(() => initializeApollo(state), [state]);
119};