@trackunit/react-graphql-hooks 0.0.1-alpha-11d90c6ca6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/index.cjs.d.ts +1 -0
- package/index.cjs.js +283 -0
- package/index.esm.js +257 -0
- package/package.json +17 -0
- package/src/index.d.ts +3 -0
- package/src/translation.d.ts +34 -0
- package/src/unionEdgesByNodeKey.d.ts +12 -0
- package/src/usePaginationQuery.d.ts +60 -0
- package/src/useRelayPagination.d.ts +41 -0
- package/translation.cjs.js +7 -0
- package/translation.cjs10.js +7 -0
- package/translation.cjs11.js +7 -0
- package/translation.cjs12.js +7 -0
- package/translation.cjs13.js +7 -0
- package/translation.cjs14.js +7 -0
- package/translation.cjs15.js +7 -0
- package/translation.cjs16.js +7 -0
- package/translation.cjs17.js +7 -0
- package/translation.cjs2.js +7 -0
- package/translation.cjs3.js +7 -0
- package/translation.cjs4.js +7 -0
- package/translation.cjs5.js +7 -0
- package/translation.cjs6.js +7 -0
- package/translation.cjs7.js +7 -0
- package/translation.cjs8.js +7 -0
- package/translation.cjs9.js +7 -0
- package/translation.esm.js +5 -0
- package/translation.esm10.js +5 -0
- package/translation.esm11.js +5 -0
- package/translation.esm12.js +5 -0
- package/translation.esm13.js +5 -0
- package/translation.esm14.js +5 -0
- package/translation.esm15.js +5 -0
- package/translation.esm16.js +5 -0
- package/translation.esm17.js +5 -0
- package/translation.esm2.js +5 -0
- package/translation.esm3.js +5 -0
- package/translation.esm4.js +5 -0
- package/translation.esm5.js +5 -0
- package/translation.esm6.js +5 -0
- package/translation.esm7.js +5 -0
- package/translation.esm8.js +5 -0
- package/translation.esm9.js +5 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
> **⚠️ Beta**
|
|
2
|
+
>
|
|
3
|
+
> This is a beta version and subject to change without notice.
|
|
4
|
+
|
|
5
|
+
# Trackunit GraphQL Hooks
|
|
6
|
+
The purpose of this library is to contain the graphql-hooks library and its dependencies.
|
|
7
|
+
These are just helper hooks for making working with GraphQL easier.
|
|
8
|
+
|
|
9
|
+
For more info and a full guide on Iris App SDK Development, please visit our [Developer Hub](https://developers.trackunit.com/).
|
|
10
|
+
|
|
11
|
+
## Development
|
|
12
|
+
At this point this library is only developed by Trackunit Employees.
|
|
13
|
+
For development related information see the [development readme](https://github.com/Trackunit/manager/blob/master/libs/react/components/DEVELOPMENT.md).
|
|
14
|
+
|
|
15
|
+
## Trackunit
|
|
16
|
+
This package was developed by Trackunit ApS.
|
|
17
|
+
Trackunit is the leading SaaS-based IoT solution for the construction industry, offering an ecosystem of hardware, fleet management software & telematics.
|
|
18
|
+
|
|
19
|
+

|
package/index.cjs.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index";
|
package/index.cjs.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
require('react/jsx-runtime');
|
|
6
|
+
var i18nLibraryTranslation = require('@trackunit/i18n-library-translation');
|
|
7
|
+
var sharedUtils = require('@trackunit/shared-utils');
|
|
8
|
+
var ApolloClient = require('@apollo/client');
|
|
9
|
+
var react = require('react');
|
|
10
|
+
|
|
11
|
+
function _interopNamespace(e) {
|
|
12
|
+
if (e && e.__esModule) return e;
|
|
13
|
+
var n = Object.create(null);
|
|
14
|
+
if (e) {
|
|
15
|
+
Object.keys(e).forEach(function (k) {
|
|
16
|
+
if (k !== 'default') {
|
|
17
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
18
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () { return e[k]; }
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
n["default"] = e;
|
|
26
|
+
return Object.freeze(n);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var ApolloClient__namespace = /*#__PURE__*/_interopNamespace(ApolloClient);
|
|
30
|
+
|
|
31
|
+
var defaultTranslations = {
|
|
32
|
+
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** The translation namespace for this library */
|
|
36
|
+
const namespace = "react.graphql-hooks";
|
|
37
|
+
/**
|
|
38
|
+
* The TranslationResource for this Library.
|
|
39
|
+
* Holds lazy loaded imports for all languages supported by the library.
|
|
40
|
+
*
|
|
41
|
+
* This is used to register the translations for this library before initializing i18next.
|
|
42
|
+
*/
|
|
43
|
+
const translations = {
|
|
44
|
+
ns: namespace,
|
|
45
|
+
default: defaultTranslations,
|
|
46
|
+
languages: {
|
|
47
|
+
de: () => Promise.resolve().then(function () { return require('./translation.cjs.js'); }),
|
|
48
|
+
da: () => Promise.resolve().then(function () { return require('./translation.cjs2.js'); }),
|
|
49
|
+
cs: () => Promise.resolve().then(function () { return require('./translation.cjs3.js'); }),
|
|
50
|
+
nl: () => Promise.resolve().then(function () { return require('./translation.cjs4.js'); }),
|
|
51
|
+
fr: () => Promise.resolve().then(function () { return require('./translation.cjs5.js'); }),
|
|
52
|
+
fi: () => Promise.resolve().then(function () { return require('./translation.cjs6.js'); }),
|
|
53
|
+
hu: () => Promise.resolve().then(function () { return require('./translation.cjs7.js'); }),
|
|
54
|
+
it: () => Promise.resolve().then(function () { return require('./translation.cjs8.js'); }),
|
|
55
|
+
nb: () => Promise.resolve().then(function () { return require('./translation.cjs9.js'); }),
|
|
56
|
+
pl: () => Promise.resolve().then(function () { return require('./translation.cjs10.js'); }),
|
|
57
|
+
pt: () => Promise.resolve().then(function () { return require('./translation.cjs11.js'); }),
|
|
58
|
+
ru: () => Promise.resolve().then(function () { return require('./translation.cjs12.js'); }),
|
|
59
|
+
ro: () => Promise.resolve().then(function () { return require('./translation.cjs13.js'); }),
|
|
60
|
+
es: () => Promise.resolve().then(function () { return require('./translation.cjs14.js'); }),
|
|
61
|
+
sv: () => Promise.resolve().then(function () { return require('./translation.cjs15.js'); }),
|
|
62
|
+
ja: () => Promise.resolve().then(function () { return require('./translation.cjs16.js'); }),
|
|
63
|
+
th: () => Promise.resolve().then(function () { return require('./translation.cjs17.js'); }),
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Registers the translations for this library
|
|
68
|
+
*/
|
|
69
|
+
const setupLibraryTranslations = () => {
|
|
70
|
+
i18nLibraryTranslation.registerTranslations(translations);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Creates a union of two arrays of edges by a key on the node.
|
|
75
|
+
*
|
|
76
|
+
* @template TEdge
|
|
77
|
+
* @param key The key to use to determine uniqueness
|
|
78
|
+
* @param previousEdges The previous array of edges to merge with the new array
|
|
79
|
+
* @param newEdges The new array of edges to merge with the previous array
|
|
80
|
+
* @returns {TEdge[]} A new array with the edges from the previous array and the new array, but only if the item's key is unique.
|
|
81
|
+
*/
|
|
82
|
+
const unionEdgesByNodeKey = (key, previousEdges, newEdges) => {
|
|
83
|
+
const previousIds = (previousEdges === null || previousEdges === void 0 ? void 0 : previousEdges.map(edge => edge.node[key])) || [];
|
|
84
|
+
const newIds = (newEdges === null || newEdges === void 0 ? void 0 : newEdges.map(edge => edge.node[key])) || [];
|
|
85
|
+
const mergedIds = [...previousIds, ...newIds];
|
|
86
|
+
const uniqueIds = [...new Set(mergedIds)];
|
|
87
|
+
return uniqueIds
|
|
88
|
+
.map(id => {
|
|
89
|
+
const previousEdge = previousEdges === null || previousEdges === void 0 ? void 0 : previousEdges.find(edge => edge.node[key] === id);
|
|
90
|
+
const newEdge = newEdges === null || newEdges === void 0 ? void 0 : newEdges.find(edge => edge.node[key] === id);
|
|
91
|
+
return newEdge || previousEdge;
|
|
92
|
+
})
|
|
93
|
+
.filter(sharedUtils.truthy);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Custom hook for handling Relay pagination in tables.
|
|
98
|
+
*
|
|
99
|
+
* @param {RelayPaginationProps} props - The props object containing pagination configuration.
|
|
100
|
+
* @returns {RelayPaginationSupport} An object containing functions and state for managing Relay pagination.
|
|
101
|
+
*/
|
|
102
|
+
const useRelayPagination = (props = { pageSize: 50 }) => {
|
|
103
|
+
const [variables, setVariables] = react.useState({ first: props.pageSize });
|
|
104
|
+
const [pageInfo, setPageInfo] = react.useState();
|
|
105
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
106
|
+
const nextPage = react.useCallback(() => {
|
|
107
|
+
if (pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.hasNextPage) {
|
|
108
|
+
setVariables({
|
|
109
|
+
after: pageInfo.endCursor === null ? undefined : pageInfo.endCursor,
|
|
110
|
+
first: props.pageSize,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}, [pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.endCursor, pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.hasNextPage, props.pageSize]);
|
|
114
|
+
const previousPage = react.useCallback(() => {
|
|
115
|
+
if (pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.hasPreviousPage) {
|
|
116
|
+
setVariables({
|
|
117
|
+
before: pageInfo.startCursor === null ? undefined : pageInfo.startCursor,
|
|
118
|
+
last: props.pageSize,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}, [pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.hasPreviousPage, pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.startCursor, props.pageSize]);
|
|
122
|
+
const reset = react.useCallback(() => {
|
|
123
|
+
setVariables({
|
|
124
|
+
last: undefined,
|
|
125
|
+
before: undefined,
|
|
126
|
+
after: undefined,
|
|
127
|
+
first: props.pageSize,
|
|
128
|
+
});
|
|
129
|
+
if (props.onReset) {
|
|
130
|
+
props.onReset();
|
|
131
|
+
}
|
|
132
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
133
|
+
}, [props.onReset, props.pageSize]);
|
|
134
|
+
return react.useMemo(() => {
|
|
135
|
+
return {
|
|
136
|
+
variables,
|
|
137
|
+
table: {
|
|
138
|
+
nextPage,
|
|
139
|
+
previousPage,
|
|
140
|
+
isLoading,
|
|
141
|
+
setIsLoading,
|
|
142
|
+
reset,
|
|
143
|
+
pageInfo: pageInfo === null ? undefined : pageInfo,
|
|
144
|
+
setPageInfo,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}, [variables, nextPage, previousPage, isLoading, reset, pageInfo]);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* This hook is used to fetch data from a GraphQL query and support pagination, it will help maintain the data and the pagination.
|
|
152
|
+
*
|
|
153
|
+
* Data will be the combined data from all pages, if you merge in the updateQuery.
|
|
154
|
+
*
|
|
155
|
+
* It will use pagination.fetchNext to fetch next page and call updateQuery where you can merge the data together.
|
|
156
|
+
*
|
|
157
|
+
* lastFetchedData is used to store the last fetched data, so you can update data according to last page.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
*
|
|
161
|
+
* const {
|
|
162
|
+
* data,
|
|
163
|
+
* loading,
|
|
164
|
+
* pagination,
|
|
165
|
+
* lastFetchedData,
|
|
166
|
+
*} = usePaginationQuery({
|
|
167
|
+
* query: myLazyQuery, // <-- myLazyQuery is the graphql LazyQuery to run pagination for.
|
|
168
|
+
* updateQuery: (previous, newData) => {
|
|
169
|
+
* // Here you can use the unionEdgesByNodeKey utils to merge your edges together.
|
|
170
|
+
* if (newData?.assets?.edges) {
|
|
171
|
+
* return {
|
|
172
|
+
* data: {
|
|
173
|
+
* ...newData,
|
|
174
|
+
* assets: {
|
|
175
|
+
* ...newData.assets,
|
|
176
|
+
* edges: unionEdgesByNodeKey("id", previous?.assets?.edges, newData.assets.edges) || [],
|
|
177
|
+
* },
|
|
178
|
+
* },
|
|
179
|
+
* pageInfo: newData.assets.pageInfo,
|
|
180
|
+
* };
|
|
181
|
+
* }
|
|
182
|
+
* return { data: newData, pageInfo: newData.assets.pageInfo };
|
|
183
|
+
* },
|
|
184
|
+
* });
|
|
185
|
+
* @template TData
|
|
186
|
+
* @template TVariables
|
|
187
|
+
* @param {ApolloClient.TypedDocumentNode<TData, TVariables>} document - The GraphQL document (query or mutation).
|
|
188
|
+
* @param {PaginationQueryProps<TData, TVariables>} props - The properties for configuring the query. This includes the `updateQuery` function for merging new and existing data, and options for pagination such as `pageSize`. Also includes other lazy query hook options from Apollo Client.
|
|
189
|
+
* @returns {*} {PaginationQuery}
|
|
190
|
+
*/
|
|
191
|
+
const usePaginationQuery = (document, props) => {
|
|
192
|
+
const [lastFetchedData, setLastFetchedData] = react.useState();
|
|
193
|
+
const [resetTrigger, setResetTrigger] = react.useState(0);
|
|
194
|
+
const [, { fetchMore, networkStatus }] = ApolloClient__namespace.useLazyQuery(document, Object.assign(Object.assign({}, props), { notifyOnNetworkStatusChange: true, onCompleted: () => {
|
|
195
|
+
if (networkStatus === ApolloClient__namespace.NetworkStatus.refetch) {
|
|
196
|
+
// trigger reset for refetchQueries for the provided document.
|
|
197
|
+
setResetTrigger(prev => prev + 1);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
// This is safe since we have no cache
|
|
201
|
+
// and without it it will make a magic extra call to the query check this
|
|
202
|
+
// https://stackoverflow.com/questions/66441463/fetchmore-request-executed-twice-every-time
|
|
203
|
+
nextFetchPolicy: "network-only", initialFetchPolicy: "network-only" }));
|
|
204
|
+
const [data, setData] = react.useState();
|
|
205
|
+
const onReset = react.useCallback(() => {
|
|
206
|
+
setResetTrigger(prev => prev + 1);
|
|
207
|
+
}, []);
|
|
208
|
+
const { table: { setIsLoading, isLoading, setPageInfo, pageInfo, reset, nextPage, previousPage }, variables: { first, after }, } = useRelayPagination({
|
|
209
|
+
pageSize: props.pageSize || 50,
|
|
210
|
+
onReset,
|
|
211
|
+
});
|
|
212
|
+
const doFetchMore = react.useCallback((variables, prev) => {
|
|
213
|
+
setIsLoading(true);
|
|
214
|
+
const abortController = new AbortController();
|
|
215
|
+
/**
|
|
216
|
+
* To support pagination with page and pageSize, we need to convert the variables from first and after.
|
|
217
|
+
*/
|
|
218
|
+
const fetchMoreVariables = Object.assign(Object.assign({}, props.variables), (props.variables && "page" in props.variables && "pageSize" in props.variables
|
|
219
|
+
? // eslint-disable-next-line local-rules/no-typescript-assertion
|
|
220
|
+
{ pageSize: variables.first, page: Number(variables.after) || 0 }
|
|
221
|
+
: Object.assign({}, variables)));
|
|
222
|
+
fetchMore({
|
|
223
|
+
variables: fetchMoreVariables,
|
|
224
|
+
updateQuery: (_, { fetchMoreResult }) => {
|
|
225
|
+
if (abortController.signal.aborted) {
|
|
226
|
+
// if prev does not hold any data we don't want to return it,
|
|
227
|
+
// since it will make the cache output an error to the console.
|
|
228
|
+
// https://github.com/apollographql/apollo-client/issues/8677
|
|
229
|
+
if (prev) {
|
|
230
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
231
|
+
return Object.keys(prev).length === 0 ? undefined : prev;
|
|
232
|
+
}
|
|
233
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
setLastFetchedData(fetchMoreResult);
|
|
237
|
+
const result = props.updateQuery(prev, fetchMoreResult);
|
|
238
|
+
setPageInfo(result.pageInfo || null);
|
|
239
|
+
setData(result.data);
|
|
240
|
+
setIsLoading(false);
|
|
241
|
+
return result.data;
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
return abortController;
|
|
245
|
+
}, [setIsLoading, props, fetchMore, setPageInfo]);
|
|
246
|
+
react.useEffect(() => {
|
|
247
|
+
setData(undefined);
|
|
248
|
+
}, [props.variables]);
|
|
249
|
+
react.useEffect(() => {
|
|
250
|
+
const abortController = doFetchMore({ first: first, last: undefined, before: undefined, after: undefined }, undefined);
|
|
251
|
+
return () => {
|
|
252
|
+
abortController.abort();
|
|
253
|
+
};
|
|
254
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
255
|
+
}, [props.variables, resetTrigger]);
|
|
256
|
+
react.useEffect(() => {
|
|
257
|
+
if (after) {
|
|
258
|
+
doFetchMore({ first, after }, data);
|
|
259
|
+
}
|
|
260
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
261
|
+
}, [after]);
|
|
262
|
+
return react.useMemo(() => {
|
|
263
|
+
return {
|
|
264
|
+
data,
|
|
265
|
+
loading: isLoading,
|
|
266
|
+
pagination: { setIsLoading, isLoading, setPageInfo, pageInfo, reset, nextPage, previousPage },
|
|
267
|
+
lastFetchedData,
|
|
268
|
+
};
|
|
269
|
+
}, [data, isLoading, setIsLoading, setPageInfo, pageInfo, reset, nextPage, previousPage, lastFetchedData]);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/*
|
|
273
|
+
* ----------------------------
|
|
274
|
+
* | SETUP TRANSLATIONS START |
|
|
275
|
+
* ----------------------------
|
|
276
|
+
* This import and function call is needed to register translations for this library.
|
|
277
|
+
* Do not remove this if this library has translations.
|
|
278
|
+
*/
|
|
279
|
+
setupLibraryTranslations();
|
|
280
|
+
|
|
281
|
+
exports.unionEdgesByNodeKey = unionEdgesByNodeKey;
|
|
282
|
+
exports.usePaginationQuery = usePaginationQuery;
|
|
283
|
+
exports.useRelayPagination = useRelayPagination;
|
package/index.esm.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import 'react/jsx-runtime';
|
|
2
|
+
import { registerTranslations } from '@trackunit/i18n-library-translation';
|
|
3
|
+
import { truthy } from '@trackunit/shared-utils';
|
|
4
|
+
import * as ApolloClient from '@apollo/client';
|
|
5
|
+
import { useState, useCallback, useMemo, useEffect } from 'react';
|
|
6
|
+
|
|
7
|
+
var defaultTranslations = {
|
|
8
|
+
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** The translation namespace for this library */
|
|
12
|
+
const namespace = "react.graphql-hooks";
|
|
13
|
+
/**
|
|
14
|
+
* The TranslationResource for this Library.
|
|
15
|
+
* Holds lazy loaded imports for all languages supported by the library.
|
|
16
|
+
*
|
|
17
|
+
* This is used to register the translations for this library before initializing i18next.
|
|
18
|
+
*/
|
|
19
|
+
const translations = {
|
|
20
|
+
ns: namespace,
|
|
21
|
+
default: defaultTranslations,
|
|
22
|
+
languages: {
|
|
23
|
+
de: () => import('./translation.esm.js'),
|
|
24
|
+
da: () => import('./translation.esm2.js'),
|
|
25
|
+
cs: () => import('./translation.esm3.js'),
|
|
26
|
+
nl: () => import('./translation.esm4.js'),
|
|
27
|
+
fr: () => import('./translation.esm5.js'),
|
|
28
|
+
fi: () => import('./translation.esm6.js'),
|
|
29
|
+
hu: () => import('./translation.esm7.js'),
|
|
30
|
+
it: () => import('./translation.esm8.js'),
|
|
31
|
+
nb: () => import('./translation.esm9.js'),
|
|
32
|
+
pl: () => import('./translation.esm10.js'),
|
|
33
|
+
pt: () => import('./translation.esm11.js'),
|
|
34
|
+
ru: () => import('./translation.esm12.js'),
|
|
35
|
+
ro: () => import('./translation.esm13.js'),
|
|
36
|
+
es: () => import('./translation.esm14.js'),
|
|
37
|
+
sv: () => import('./translation.esm15.js'),
|
|
38
|
+
ja: () => import('./translation.esm16.js'),
|
|
39
|
+
th: () => import('./translation.esm17.js'),
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Registers the translations for this library
|
|
44
|
+
*/
|
|
45
|
+
const setupLibraryTranslations = () => {
|
|
46
|
+
registerTranslations(translations);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates a union of two arrays of edges by a key on the node.
|
|
51
|
+
*
|
|
52
|
+
* @template TEdge
|
|
53
|
+
* @param key The key to use to determine uniqueness
|
|
54
|
+
* @param previousEdges The previous array of edges to merge with the new array
|
|
55
|
+
* @param newEdges The new array of edges to merge with the previous array
|
|
56
|
+
* @returns {TEdge[]} A new array with the edges from the previous array and the new array, but only if the item's key is unique.
|
|
57
|
+
*/
|
|
58
|
+
const unionEdgesByNodeKey = (key, previousEdges, newEdges) => {
|
|
59
|
+
const previousIds = (previousEdges === null || previousEdges === void 0 ? void 0 : previousEdges.map(edge => edge.node[key])) || [];
|
|
60
|
+
const newIds = (newEdges === null || newEdges === void 0 ? void 0 : newEdges.map(edge => edge.node[key])) || [];
|
|
61
|
+
const mergedIds = [...previousIds, ...newIds];
|
|
62
|
+
const uniqueIds = [...new Set(mergedIds)];
|
|
63
|
+
return uniqueIds
|
|
64
|
+
.map(id => {
|
|
65
|
+
const previousEdge = previousEdges === null || previousEdges === void 0 ? void 0 : previousEdges.find(edge => edge.node[key] === id);
|
|
66
|
+
const newEdge = newEdges === null || newEdges === void 0 ? void 0 : newEdges.find(edge => edge.node[key] === id);
|
|
67
|
+
return newEdge || previousEdge;
|
|
68
|
+
})
|
|
69
|
+
.filter(truthy);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Custom hook for handling Relay pagination in tables.
|
|
74
|
+
*
|
|
75
|
+
* @param {RelayPaginationProps} props - The props object containing pagination configuration.
|
|
76
|
+
* @returns {RelayPaginationSupport} An object containing functions and state for managing Relay pagination.
|
|
77
|
+
*/
|
|
78
|
+
const useRelayPagination = (props = { pageSize: 50 }) => {
|
|
79
|
+
const [variables, setVariables] = useState({ first: props.pageSize });
|
|
80
|
+
const [pageInfo, setPageInfo] = useState();
|
|
81
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
82
|
+
const nextPage = useCallback(() => {
|
|
83
|
+
if (pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.hasNextPage) {
|
|
84
|
+
setVariables({
|
|
85
|
+
after: pageInfo.endCursor === null ? undefined : pageInfo.endCursor,
|
|
86
|
+
first: props.pageSize,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}, [pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.endCursor, pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.hasNextPage, props.pageSize]);
|
|
90
|
+
const previousPage = useCallback(() => {
|
|
91
|
+
if (pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.hasPreviousPage) {
|
|
92
|
+
setVariables({
|
|
93
|
+
before: pageInfo.startCursor === null ? undefined : pageInfo.startCursor,
|
|
94
|
+
last: props.pageSize,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}, [pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.hasPreviousPage, pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.startCursor, props.pageSize]);
|
|
98
|
+
const reset = useCallback(() => {
|
|
99
|
+
setVariables({
|
|
100
|
+
last: undefined,
|
|
101
|
+
before: undefined,
|
|
102
|
+
after: undefined,
|
|
103
|
+
first: props.pageSize,
|
|
104
|
+
});
|
|
105
|
+
if (props.onReset) {
|
|
106
|
+
props.onReset();
|
|
107
|
+
}
|
|
108
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
109
|
+
}, [props.onReset, props.pageSize]);
|
|
110
|
+
return useMemo(() => {
|
|
111
|
+
return {
|
|
112
|
+
variables,
|
|
113
|
+
table: {
|
|
114
|
+
nextPage,
|
|
115
|
+
previousPage,
|
|
116
|
+
isLoading,
|
|
117
|
+
setIsLoading,
|
|
118
|
+
reset,
|
|
119
|
+
pageInfo: pageInfo === null ? undefined : pageInfo,
|
|
120
|
+
setPageInfo,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}, [variables, nextPage, previousPage, isLoading, reset, pageInfo]);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* This hook is used to fetch data from a GraphQL query and support pagination, it will help maintain the data and the pagination.
|
|
128
|
+
*
|
|
129
|
+
* Data will be the combined data from all pages, if you merge in the updateQuery.
|
|
130
|
+
*
|
|
131
|
+
* It will use pagination.fetchNext to fetch next page and call updateQuery where you can merge the data together.
|
|
132
|
+
*
|
|
133
|
+
* lastFetchedData is used to store the last fetched data, so you can update data according to last page.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
*
|
|
137
|
+
* const {
|
|
138
|
+
* data,
|
|
139
|
+
* loading,
|
|
140
|
+
* pagination,
|
|
141
|
+
* lastFetchedData,
|
|
142
|
+
*} = usePaginationQuery({
|
|
143
|
+
* query: myLazyQuery, // <-- myLazyQuery is the graphql LazyQuery to run pagination for.
|
|
144
|
+
* updateQuery: (previous, newData) => {
|
|
145
|
+
* // Here you can use the unionEdgesByNodeKey utils to merge your edges together.
|
|
146
|
+
* if (newData?.assets?.edges) {
|
|
147
|
+
* return {
|
|
148
|
+
* data: {
|
|
149
|
+
* ...newData,
|
|
150
|
+
* assets: {
|
|
151
|
+
* ...newData.assets,
|
|
152
|
+
* edges: unionEdgesByNodeKey("id", previous?.assets?.edges, newData.assets.edges) || [],
|
|
153
|
+
* },
|
|
154
|
+
* },
|
|
155
|
+
* pageInfo: newData.assets.pageInfo,
|
|
156
|
+
* };
|
|
157
|
+
* }
|
|
158
|
+
* return { data: newData, pageInfo: newData.assets.pageInfo };
|
|
159
|
+
* },
|
|
160
|
+
* });
|
|
161
|
+
* @template TData
|
|
162
|
+
* @template TVariables
|
|
163
|
+
* @param {ApolloClient.TypedDocumentNode<TData, TVariables>} document - The GraphQL document (query or mutation).
|
|
164
|
+
* @param {PaginationQueryProps<TData, TVariables>} props - The properties for configuring the query. This includes the `updateQuery` function for merging new and existing data, and options for pagination such as `pageSize`. Also includes other lazy query hook options from Apollo Client.
|
|
165
|
+
* @returns {*} {PaginationQuery}
|
|
166
|
+
*/
|
|
167
|
+
const usePaginationQuery = (document, props) => {
|
|
168
|
+
const [lastFetchedData, setLastFetchedData] = useState();
|
|
169
|
+
const [resetTrigger, setResetTrigger] = useState(0);
|
|
170
|
+
const [, { fetchMore, networkStatus }] = ApolloClient.useLazyQuery(document, Object.assign(Object.assign({}, props), { notifyOnNetworkStatusChange: true, onCompleted: () => {
|
|
171
|
+
if (networkStatus === ApolloClient.NetworkStatus.refetch) {
|
|
172
|
+
// trigger reset for refetchQueries for the provided document.
|
|
173
|
+
setResetTrigger(prev => prev + 1);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
// This is safe since we have no cache
|
|
177
|
+
// and without it it will make a magic extra call to the query check this
|
|
178
|
+
// https://stackoverflow.com/questions/66441463/fetchmore-request-executed-twice-every-time
|
|
179
|
+
nextFetchPolicy: "network-only", initialFetchPolicy: "network-only" }));
|
|
180
|
+
const [data, setData] = useState();
|
|
181
|
+
const onReset = useCallback(() => {
|
|
182
|
+
setResetTrigger(prev => prev + 1);
|
|
183
|
+
}, []);
|
|
184
|
+
const { table: { setIsLoading, isLoading, setPageInfo, pageInfo, reset, nextPage, previousPage }, variables: { first, after }, } = useRelayPagination({
|
|
185
|
+
pageSize: props.pageSize || 50,
|
|
186
|
+
onReset,
|
|
187
|
+
});
|
|
188
|
+
const doFetchMore = useCallback((variables, prev) => {
|
|
189
|
+
setIsLoading(true);
|
|
190
|
+
const abortController = new AbortController();
|
|
191
|
+
/**
|
|
192
|
+
* To support pagination with page and pageSize, we need to convert the variables from first and after.
|
|
193
|
+
*/
|
|
194
|
+
const fetchMoreVariables = Object.assign(Object.assign({}, props.variables), (props.variables && "page" in props.variables && "pageSize" in props.variables
|
|
195
|
+
? // eslint-disable-next-line local-rules/no-typescript-assertion
|
|
196
|
+
{ pageSize: variables.first, page: Number(variables.after) || 0 }
|
|
197
|
+
: Object.assign({}, variables)));
|
|
198
|
+
fetchMore({
|
|
199
|
+
variables: fetchMoreVariables,
|
|
200
|
+
updateQuery: (_, { fetchMoreResult }) => {
|
|
201
|
+
if (abortController.signal.aborted) {
|
|
202
|
+
// if prev does not hold any data we don't want to return it,
|
|
203
|
+
// since it will make the cache output an error to the console.
|
|
204
|
+
// https://github.com/apollographql/apollo-client/issues/8677
|
|
205
|
+
if (prev) {
|
|
206
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
207
|
+
return Object.keys(prev).length === 0 ? undefined : prev;
|
|
208
|
+
}
|
|
209
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
setLastFetchedData(fetchMoreResult);
|
|
213
|
+
const result = props.updateQuery(prev, fetchMoreResult);
|
|
214
|
+
setPageInfo(result.pageInfo || null);
|
|
215
|
+
setData(result.data);
|
|
216
|
+
setIsLoading(false);
|
|
217
|
+
return result.data;
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
return abortController;
|
|
221
|
+
}, [setIsLoading, props, fetchMore, setPageInfo]);
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
setData(undefined);
|
|
224
|
+
}, [props.variables]);
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
const abortController = doFetchMore({ first: first, last: undefined, before: undefined, after: undefined }, undefined);
|
|
227
|
+
return () => {
|
|
228
|
+
abortController.abort();
|
|
229
|
+
};
|
|
230
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
231
|
+
}, [props.variables, resetTrigger]);
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
if (after) {
|
|
234
|
+
doFetchMore({ first, after }, data);
|
|
235
|
+
}
|
|
236
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
237
|
+
}, [after]);
|
|
238
|
+
return useMemo(() => {
|
|
239
|
+
return {
|
|
240
|
+
data,
|
|
241
|
+
loading: isLoading,
|
|
242
|
+
pagination: { setIsLoading, isLoading, setPageInfo, pageInfo, reset, nextPage, previousPage },
|
|
243
|
+
lastFetchedData,
|
|
244
|
+
};
|
|
245
|
+
}, [data, isLoading, setIsLoading, setPageInfo, pageInfo, reset, nextPage, previousPage, lastFetchedData]);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/*
|
|
249
|
+
* ----------------------------
|
|
250
|
+
* | SETUP TRANSLATIONS START |
|
|
251
|
+
* ----------------------------
|
|
252
|
+
* This import and function call is needed to register translations for this library.
|
|
253
|
+
* Do not remove this if this library has translations.
|
|
254
|
+
*/
|
|
255
|
+
setupLibraryTranslations();
|
|
256
|
+
|
|
257
|
+
export { unionEdgesByNodeKey, usePaginationQuery, useRelayPagination };
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@trackunit/react-graphql-hooks",
|
|
3
|
+
"version": "0.0.1-alpha-11d90c6ca6.0",
|
|
4
|
+
"repository": "https://github.com/Trackunit/manager",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=18.x"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@apollo/client": "^3.7.10",
|
|
11
|
+
"react": "^18.2.0",
|
|
12
|
+
"@trackunit/i18n-library-translation": "*",
|
|
13
|
+
"@trackunit/shared-utils": "*"
|
|
14
|
+
},
|
|
15
|
+
"module": "./index.esm.js",
|
|
16
|
+
"main": "./index.cjs.js"
|
|
17
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { NamespaceTransProps, TransForLibs, TranslationResource } from "@trackunit/i18n-library-translation";
|
|
3
|
+
import defaultTranslations from "./locales/en/translation.json";
|
|
4
|
+
/** A type for all available translation keys in this library */
|
|
5
|
+
export type TranslationKeys = keyof typeof defaultTranslations;
|
|
6
|
+
/** The translation namespace for this library */
|
|
7
|
+
export declare const namespace = "react.graphql-hooks";
|
|
8
|
+
/**
|
|
9
|
+
* The TranslationResource for this Library.
|
|
10
|
+
* Holds lazy loaded imports for all languages supported by the library.
|
|
11
|
+
*
|
|
12
|
+
* This is used to register the translations for this library before initializing i18next.
|
|
13
|
+
*/
|
|
14
|
+
export declare const translations: TranslationResource<TranslationKeys>;
|
|
15
|
+
/**
|
|
16
|
+
* Local useTranslation for this specific library
|
|
17
|
+
*/
|
|
18
|
+
export declare const useTranslation: () => [TransForLibs<never>, import("i18next").i18n, boolean] & {
|
|
19
|
+
t: TransForLibs<never>;
|
|
20
|
+
i18n: import("i18next").i18n;
|
|
21
|
+
ready: boolean;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Type of the t function for the local useTranslation for this specific library
|
|
25
|
+
*/
|
|
26
|
+
export type TranslationFunction = TransForLibs<TranslationKeys>;
|
|
27
|
+
/**
|
|
28
|
+
* Trans for this specific library.
|
|
29
|
+
*/
|
|
30
|
+
export declare const Trans: (props: NamespaceTransProps<TranslationKeys>) => JSX.Element;
|
|
31
|
+
/**
|
|
32
|
+
* Registers the translations for this library
|
|
33
|
+
*/
|
|
34
|
+
export declare const setupLibraryTranslations: () => void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a union of two arrays of edges by a key on the node.
|
|
3
|
+
*
|
|
4
|
+
* @template TEdge
|
|
5
|
+
* @param key The key to use to determine uniqueness
|
|
6
|
+
* @param previousEdges The previous array of edges to merge with the new array
|
|
7
|
+
* @param newEdges The new array of edges to merge with the previous array
|
|
8
|
+
* @returns {TEdge[]} A new array with the edges from the previous array and the new array, but only if the item's key is unique.
|
|
9
|
+
*/
|
|
10
|
+
export declare const unionEdgesByNodeKey: <TEdge extends {
|
|
11
|
+
node: { [K in keyof TEdge["node"]]: unknown; };
|
|
12
|
+
}>(key: keyof TEdge["node"], previousEdges: TEdge[] | null | undefined, newEdges: TEdge[] | null | undefined) => TEdge[];
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as ApolloClient from "@apollo/client";
|
|
2
|
+
import { RelayPageInfo, RelayTableSupport } from "./useRelayPagination";
|
|
3
|
+
/**
|
|
4
|
+
* This type is used to return the data from the query, the pagination object and the last fetched data.
|
|
5
|
+
*/
|
|
6
|
+
export type PaginationQuery<TData> = {
|
|
7
|
+
data: TData | undefined;
|
|
8
|
+
loading: boolean;
|
|
9
|
+
lastFetchedData?: TData;
|
|
10
|
+
pagination: RelayTableSupport;
|
|
11
|
+
};
|
|
12
|
+
export type PaginationQueryProps<TData, TVariables extends ApolloClient.OperationVariables = ApolloClient.OperationVariables> = {
|
|
13
|
+
updateQuery: (previous: TData | undefined, newData: TData) => {
|
|
14
|
+
data: TData;
|
|
15
|
+
pageInfo?: RelayPageInfo | null;
|
|
16
|
+
};
|
|
17
|
+
pageSize?: number;
|
|
18
|
+
} & ApolloClient.LazyQueryHookOptions<TData, TVariables>;
|
|
19
|
+
/**
|
|
20
|
+
* This hook is used to fetch data from a GraphQL query and support pagination, it will help maintain the data and the pagination.
|
|
21
|
+
*
|
|
22
|
+
* Data will be the combined data from all pages, if you merge in the updateQuery.
|
|
23
|
+
*
|
|
24
|
+
* It will use pagination.fetchNext to fetch next page and call updateQuery where you can merge the data together.
|
|
25
|
+
*
|
|
26
|
+
* lastFetchedData is used to store the last fetched data, so you can update data according to last page.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
*
|
|
30
|
+
* const {
|
|
31
|
+
* data,
|
|
32
|
+
* loading,
|
|
33
|
+
* pagination,
|
|
34
|
+
* lastFetchedData,
|
|
35
|
+
*} = usePaginationQuery({
|
|
36
|
+
* query: myLazyQuery, // <-- myLazyQuery is the graphql LazyQuery to run pagination for.
|
|
37
|
+
* updateQuery: (previous, newData) => {
|
|
38
|
+
* // Here you can use the unionEdgesByNodeKey utils to merge your edges together.
|
|
39
|
+
* if (newData?.assets?.edges) {
|
|
40
|
+
* return {
|
|
41
|
+
* data: {
|
|
42
|
+
* ...newData,
|
|
43
|
+
* assets: {
|
|
44
|
+
* ...newData.assets,
|
|
45
|
+
* edges: unionEdgesByNodeKey("id", previous?.assets?.edges, newData.assets.edges) || [],
|
|
46
|
+
* },
|
|
47
|
+
* },
|
|
48
|
+
* pageInfo: newData.assets.pageInfo,
|
|
49
|
+
* };
|
|
50
|
+
* }
|
|
51
|
+
* return { data: newData, pageInfo: newData.assets.pageInfo };
|
|
52
|
+
* },
|
|
53
|
+
* });
|
|
54
|
+
* @template TData
|
|
55
|
+
* @template TVariables
|
|
56
|
+
* @param {ApolloClient.TypedDocumentNode<TData, TVariables>} document - The GraphQL document (query or mutation).
|
|
57
|
+
* @param {PaginationQueryProps<TData, TVariables>} props - The properties for configuring the query. This includes the `updateQuery` function for merging new and existing data, and options for pagination such as `pageSize`. Also includes other lazy query hook options from Apollo Client.
|
|
58
|
+
* @returns {*} {PaginationQuery}
|
|
59
|
+
*/
|
|
60
|
+
export declare const usePaginationQuery: <TData, TVariables extends ApolloClient.OperationVariables>(document: ApolloClient.TypedDocumentNode<TData, TVariables>, props: PaginationQueryProps<TData, TVariables>) => PaginationQuery<TData>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Dispatch, SetStateAction } from "react";
|
|
2
|
+
export interface RelayPaginationProps {
|
|
3
|
+
pageSize?: number;
|
|
4
|
+
onReset?: () => void;
|
|
5
|
+
}
|
|
6
|
+
export interface RelayPageInfo {
|
|
7
|
+
count?: number | null;
|
|
8
|
+
endCursor?: string | null;
|
|
9
|
+
hasNextPage?: boolean;
|
|
10
|
+
hasPreviousPage?: boolean;
|
|
11
|
+
startCursor?: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface RelayPagination {
|
|
14
|
+
nextPage: () => void;
|
|
15
|
+
previousPage: () => void;
|
|
16
|
+
pageInfo?: RelayPageInfo;
|
|
17
|
+
isLoading: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface RelayPaginationQueryVariables {
|
|
20
|
+
first?: number | null;
|
|
21
|
+
last?: number | null;
|
|
22
|
+
before?: string | null;
|
|
23
|
+
after?: string | null;
|
|
24
|
+
}
|
|
25
|
+
export interface RelayTableSupport extends RelayPagination {
|
|
26
|
+
isLoading: boolean;
|
|
27
|
+
setIsLoading: Dispatch<SetStateAction<boolean>>;
|
|
28
|
+
reset: () => void;
|
|
29
|
+
setPageInfo: Dispatch<SetStateAction<RelayPageInfo | null | undefined>>;
|
|
30
|
+
}
|
|
31
|
+
export interface RelayPaginationSupport {
|
|
32
|
+
variables: RelayPaginationQueryVariables;
|
|
33
|
+
table: RelayTableSupport;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Custom hook for handling Relay pagination in tables.
|
|
37
|
+
*
|
|
38
|
+
* @param {RelayPaginationProps} props - The props object containing pagination configuration.
|
|
39
|
+
* @returns {RelayPaginationSupport} An object containing functions and state for managing Relay pagination.
|
|
40
|
+
*/
|
|
41
|
+
export declare const useRelayPagination: (props?: RelayPaginationProps) => RelayPaginationSupport;
|