frontend/lib/apolloClient.tsx (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 = 'http://localhost:1337'} = 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
16export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__';
17let apolloClient;
18
19const authLink = setContext((_, {headers}) => {
20 // get the authentication token from local storage if it exists
21 const {token} = useAuthStore.getState();
22 // return the headers to the context so httpLink can read them
23 return {
24 headers: {
25 ...headers,
26 authorization: token ? `Bearer ${token}` : '',
27 },
28 };
29});
30
31const errorLink = onError(({graphQLErrors = []}) => {
32 const [error] = graphQLErrors;
33 console.error({graphQLErrors});
34
35 if (
36 error?.message === 'Invalid token.' ||
37 error?.message === 'User Not Found' ||
38 error?.message === 'Your account has been blocked by the administrator.'
39 ) {
40 useAuthStore.getState().setToken();
41 useAuthStore.getState().setUser();
42 localStorage.removeItem('token');
43 localStorage.removeItem('user');
44 }
45});
46
47const httpLink = uri =>
48 new HttpLink({
49 uri, // Server URL (must be absolute)
50 credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
51 });
52
53const createApolloClient = () => {
54 return new ApolloClient({
55 ssrMode: typeof window === 'undefined',
56 link: from([authLink, errorLink, httpLink(`${STRAPI_URL}/graphql`)]),
57 cache: new InMemoryCache(),
58 });
59};
60
61export const initializeApollo = (initialState = null) => {
62 const _apolloClient = apolloClient ?? createApolloClient();
63
64 // If your page has Next.js data fetching methods that use Apollo Client, the initial state gets hydrated here
65 if (initialState) {
66 // Get existing cache, loaded during client side data fetching
67 const existingCache = _apolloClient.extract();
68 // Merge the existing cache into data passed from getStaticProps/getServerSideProps
69 const data = merge(initialState, existingCache, {
70 // Combine arrays using object equality (like in sets)
71 arrayMerge: (destinationArray, sourceArray) => [
72 ...sourceArray,
73 ...destinationArray.filter(d => sourceArray.every(s => !isEqual(d, s))),
74 ],
75 });
76 // Restore the cache with the merged data
77 _apolloClient.cache.restore(data);
78 }
79
80 // For SSG and SSR always create a new Apollo Client
81 if (typeof window === 'undefined') return _apolloClient;
82
83 // Create the Apollo Client once in the client
84 if (!apolloClient) apolloClient = _apolloClient;
85 return _apolloClient;
86};
87
88export const addApolloState = (client, pageProps) => {
89 if (pageProps?.props) {
90 pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
91 }
92 return pageProps;
93};
94
95export const useApollo = pageProps => {
96 const state = pageProps[APOLLO_STATE_PROP_NAME];
97
98 return useMemo(() => initializeApollo(state), [state]);
99};