1 | import { useCallback, useContext, useEffect, useReducer, useMemo, useRef } from 'react';
|
2 | import PaginationContext from '@jetshop/core/components/Pagination/PaginationContext';
|
3 | import { useApolloClient } from 'react-apollo';
|
4 | import get from 'lodash.get';
|
5 | function getProductsFromPreviousPages(products, client, query, variables) {
|
6 | const nextVariables = Object.assign(Object.assign({}, variables), { offset: variables.offset - variables.first });
|
7 | try {
|
8 | const res = client.readQuery({
|
9 | query,
|
10 | variables: nextVariables
|
11 | });
|
12 | return getProductsFromPreviousPages(res.route.object.products.result.concat(products), client, query, nextVariables);
|
13 | }
|
14 | catch (e) {
|
15 | return { prevOffset: nextVariables.offset, products };
|
16 | }
|
17 | }
|
18 | function getProductsFromNextPages(products, client, query, variables) {
|
19 | const nextVariables = Object.assign(Object.assign({}, variables), { offset: variables.offset + variables.first });
|
20 | try {
|
21 | const res = client.readQuery({
|
22 | query,
|
23 | variables: nextVariables
|
24 | });
|
25 | return getProductsFromNextPages(products.concat(res.route.object.products.result), client, query, nextVariables);
|
26 | }
|
27 | catch (e) {
|
28 | return { nextOffset: nextVariables.offset, products };
|
29 | }
|
30 | }
|
31 | function getAdjacentProducts(products, client, query, variables) {
|
32 | const { prevOffset, products: prevProducts } = getProductsFromPreviousPages(products, client, query, variables);
|
33 | const { nextOffset, products: allProducts } = getProductsFromNextPages(prevProducts, client, query, variables);
|
34 | return {
|
35 | prevOffset,
|
36 | nextOffset,
|
37 | products: allProducts
|
38 | };
|
39 | }
|
40 | const initialState = {
|
41 | previous: false,
|
42 | next: false
|
43 | };
|
44 | function reducer(state, action) {
|
45 | switch (action.type) {
|
46 | case 'SUCCESS':
|
47 | case 'ERROR':
|
48 | return Object.assign(Object.assign({}, state), { [action.payload]: false });
|
49 | case 'FETCHING':
|
50 | return Object.assign(Object.assign({}, state), { [action.payload]: true });
|
51 | default:
|
52 | return state;
|
53 | }
|
54 | }
|
55 | const useInfinitePagination = ({ result, query }) => {
|
56 | const category = get(result, 'data.route.object', {});
|
57 | const [loading, dispatch] = useReducer(reducer, initialState);
|
58 | const { perPage, offset, goToPage } = useContext(PaginationContext);
|
59 | const totalResults = get(category, 'products.totalResults', 0);
|
60 | const client = useApolloClient();
|
61 | // When load more is clicked, the new offset for the query is changed
|
62 | const fetchProductsByOffset = useCallback(newOffset => {
|
63 | const payload = newOffset < offset ? 'previous' : 'next';
|
64 | dispatch({
|
65 | type: 'FETCHING',
|
66 | payload
|
67 | });
|
68 | client
|
69 | .query({
|
70 | query,
|
71 | variables: Object.assign(Object.assign({}, result.variables), { offset: newOffset }),
|
72 | errorPolicy: 'all',
|
73 | fetchPolicy: 'network-only'
|
74 | })
|
75 | .then(() => dispatch({
|
76 | type: 'SUCCESS',
|
77 | payload
|
78 | }), () => dispatch({
|
79 | type: 'ERROR',
|
80 | payload
|
81 | }));
|
82 | }, [offset, client, result, query]);
|
83 |
|
84 | useEffect(() => {
|
85 | if (totalResults > 0 && totalResults < offset) {
|
86 | const lastPage = Math.ceil(totalResults / perPage);
|
87 | goToPage(lastPage);
|
88 | }
|
89 | }, [totalResults, perPage, offset, goToPage]);
|
90 | const { prevOffset, nextOffset, products } = getAdjacentProducts(get(category, 'products.result', []), client, query, Object.assign(Object.assign({}, result.variables), { offset }));
|
91 |
|
92 |
|
93 | const prevProducts = useRef([]);
|
94 | const memoizedProducts = useMemo(() => {
|
95 | const productsLength = products.length;
|
96 | if (productsLength === prevProducts.current.length) {
|
97 | let hasMismatch = false;
|
98 | for (let i = 0; i < productsLength; i++) {
|
99 | if (products[i] !== prevProducts.current[i]) {
|
100 | hasMismatch = true;
|
101 | break;
|
102 | }
|
103 | }
|
104 | if (!hasMismatch) {
|
105 | return prevProducts.current;
|
106 | }
|
107 | }
|
108 | prevProducts.current = products;
|
109 | return prevProducts.current;
|
110 | }, [products]);
|
111 | return {
|
112 | previous: {
|
113 | loadingProducts: loading.previous,
|
114 | hasProducts: prevOffset >= 0,
|
115 | fetchProducts: () => fetchProductsByOffset(prevOffset),
|
116 | offset: prevOffset,
|
117 | page: prevOffset / perPage + 1
|
118 | },
|
119 | next: {
|
120 | loadingProducts: loading.next,
|
121 | hasProducts: nextOffset <= totalResults,
|
122 | fetchProducts: () => fetchProductsByOffset(nextOffset),
|
123 | offset: nextOffset,
|
124 | page: nextOffset / perPage
|
125 | },
|
126 | products: memoizedProducts
|
127 | };
|
128 | };
|
129 | export default useInfinitePagination;
|
130 |
|
\ | No newline at end of file |