Created
October 5, 2020 19:17
-
-
Save trevorblades/9f76f133147fa6d712e28d61ec5666be to your computer and use it in GitHub Desktop.
Pagination helpers in AC3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fetch from 'isomorphic-fetch'; | |
import ws from 'isomorphic-ws'; | |
import { | |
ApolloClient, | |
ApolloLink, | |
HttpLink, | |
InMemoryCache, | |
split | |
} from '@apollo/client'; | |
import {WebSocketLink} from '@apollo/client/link/ws'; | |
import { | |
getMainDefinition, | |
offsetLimitPagination | |
} from '@apollo/client/utilities'; | |
const httpLink = new HttpLink({ | |
uri: process.env.GATSBY_API_URL, | |
fetch | |
}); | |
const authLink = new ApolloLink((operation, forward) => { | |
const token = localStorage.getItem(process.env.GATSBY_TOKEN_KEY); | |
if (token) { | |
operation.setContext({ | |
headers: { | |
Authorization: `Bearer ${token}` | |
} | |
}); | |
} | |
return forward(operation); | |
}); | |
const wsLink = new WebSocketLink({ | |
uri: process.env.GATSBY_WS_URL, | |
webSocketImpl: ws, | |
options: { | |
reconnect: true, | |
connectionParams: () => ({ | |
authToken: localStorage.getItem(process.env.GATSBY_TOKEN_KEY) | |
}) | |
} | |
}); | |
const splitLink = split( | |
({query}) => { | |
const definition = getMainDefinition(query); | |
return ( | |
definition.kind === 'OperationDefinition' && | |
definition.operation === 'subscription' | |
); | |
}, | |
wsLink, | |
authLink.concat(httpLink) | |
); | |
const client = new ApolloClient({ | |
link: splitLink, | |
cache: new InMemoryCache({ | |
typePolicies: { | |
Query: { | |
fields: { | |
packs: offsetLimitPagination(['sort', 'type']) | |
} | |
} | |
} | |
}), | |
resolvers: { | |
Query: { | |
isLoggedIn: () => | |
Boolean(localStorage.getItem(process.env.GATSBY_TOKEN_KEY)) | |
}, | |
Pack: { | |
fullName: pack => | |
(pack.collection ? `${pack.collection.name}: ` : '') + pack.name | |
} | |
} | |
}); | |
export default client; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import PackGrid, {CardSkeleton, PacksLoading} from '../PackGrid'; | |
import PackLoader from './PackLoader'; | |
import PropTypes from 'prop-types'; | |
import React, {useEffect, useState} from 'react'; | |
import {NetworkStatus, gql, useQuery} from '@apollo/client'; | |
import {PACK_LISTING_FRAGMENT} from '../../utils'; | |
import {Text} from '@chakra-ui/core'; | |
const LIST_PACKS = gql` | |
query ListPacks( | |
$limit: Int! | |
$offset: Int! | |
$sort: SortOption | |
$type: PackType | |
) { | |
packs(limit: $limit, offset: $offset, sort: $sort, type: $type) { | |
...PackListingFragment | |
} | |
} | |
${PACK_LISTING_FRAGMENT} | |
`; | |
export default function ListPacks({sort, type}) { | |
const [loaded, setLoaded] = useState(false); | |
const {data, error, fetchMore, networkStatus, variables} = useQuery( | |
LIST_PACKS, | |
{ | |
notifyOnNetworkStatusChange: true, | |
variables: { | |
sort, | |
type, | |
offset: 0, | |
limit: 10 | |
} | |
} | |
); | |
useEffect(() => { | |
setLoaded(false); | |
}, [sort, type]); | |
if ( | |
networkStatus === NetworkStatus.loading || | |
networkStatus === NetworkStatus.refetch || | |
networkStatus === NetworkStatus.setVariables | |
) { | |
return <PacksLoading />; | |
} | |
if (error) { | |
return <Text color="red.500">{error.message}</Text>; | |
} | |
return ( | |
<PackGrid packs={data.packs}> | |
{!loaded && data.packs.length % variables.limit === 0 && ( | |
<PackLoader | |
setLoaded={setLoaded} | |
loading={networkStatus === NetworkStatus.fetchMore} | |
fetchMore={fetchMore} | |
offset={data.packs.length} | |
> | |
<CardSkeleton /> | |
</PackLoader> | |
)} | |
</PackGrid> | |
); | |
} | |
ListPacks.propTypes = { | |
sort: PropTypes.string.isRequired, | |
type: PropTypes.string.isRequired | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import PropTypes from 'prop-types'; | |
import React, {useCallback, useEffect, useRef} from 'react'; | |
export default function PackLoader({ | |
children, | |
offset, | |
loading, | |
fetchMore, | |
setLoaded | |
}) { | |
const loaderRef = useRef(); | |
const loadMore = useCallback( | |
entries => { | |
const target = entries[0]; | |
if (target.isIntersecting && !loading) { | |
fetchMore({ | |
variables: { | |
offset | |
} | |
}).then(({data}) => setLoaded(!data.packs.length)); | |
} | |
}, | |
[loading, fetchMore, offset, setLoaded] | |
); | |
useEffect(() => { | |
// this technique is inspired by | |
// https://medium.com/@swatisucharita94/react-infinite-scroll-with-intersection-observer-api-db3998e52d63 | |
const observer = new IntersectionObserver(loadMore, { | |
root: null, | |
rootMargin: '0px', | |
threshold: 0.25 | |
}); | |
const {current} = loaderRef; | |
observer.observe(current); | |
return () => observer.unobserve(current); | |
}, [loaderRef, loadMore]); | |
return ( | |
<> | |
{React.cloneElement(children, {ref: loaderRef})} | |
{Array.from({length: 2}, (item, index) => | |
React.cloneElement(children, {key: index}) | |
)} | |
</> | |
); | |
} | |
PackLoader.propTypes = { | |
children: PropTypes.element.isRequired, | |
offset: PropTypes.number.isRequired, | |
fetchMore: PropTypes.func.isRequired, | |
setLoaded: PropTypes.func.isRequired, | |
loading: PropTypes.bool.isRequired | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment