@dhis2/app-service-data 3.4.1 → 3.4.2
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/build/cjs/react/hooks/mergeAndCompareVariables.js +33 -0
- package/build/cjs/react/hooks/mergeAndCompareVariables.test.js +57 -0
- package/build/cjs/react/hooks/useDataQuery.js +43 -53
- package/build/cjs/react/hooks/useDataQuery.test.js +59 -0
- package/build/cjs/react/hooks/useStaticInput.js +1 -0
- package/build/es/react/hooks/mergeAndCompareVariables.js +23 -0
- package/build/es/react/hooks/mergeAndCompareVariables.test.js +53 -0
- package/build/es/react/hooks/useDataQuery.js +44 -54
- package/build/es/react/hooks/useDataQuery.test.js +59 -0
- package/build/es/react/hooks/useStaticInput.js +2 -1
- package/build/types/react/hooks/mergeAndCompareVariables.d.ts +6 -0
- package/build/types/react/hooks/useDataQuery.d.ts +2 -2
- package/package.json +2 -2
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.mergeAndCompareVariables = void 0;
|
|
7
|
+
|
|
8
|
+
var _stableVariablesHash = require("./stableVariablesHash");
|
|
9
|
+
|
|
10
|
+
const mergeAndCompareVariables = (previousVariables, newVariables, previousHash) => {
|
|
11
|
+
if (!newVariables) {
|
|
12
|
+
return {
|
|
13
|
+
identical: true,
|
|
14
|
+
mergedVariablesHash: previousHash,
|
|
15
|
+
mergedVariables: previousVariables
|
|
16
|
+
};
|
|
17
|
+
} // Use cached hash if it exists
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
const currentHash = previousHash || (0, _stableVariablesHash.stableVariablesHash)(previousVariables);
|
|
21
|
+
const mergedVariables = { ...previousVariables,
|
|
22
|
+
...newVariables
|
|
23
|
+
};
|
|
24
|
+
const mergedVariablesHash = (0, _stableVariablesHash.stableVariablesHash)(mergedVariables);
|
|
25
|
+
const identical = currentHash === mergedVariablesHash;
|
|
26
|
+
return {
|
|
27
|
+
identical,
|
|
28
|
+
mergedVariablesHash,
|
|
29
|
+
mergedVariables
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
exports.mergeAndCompareVariables = mergeAndCompareVariables;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _mergeAndCompareVariables = require("./mergeAndCompareVariables");
|
|
4
|
+
|
|
5
|
+
var _stableVariablesHash = require("./stableVariablesHash");
|
|
6
|
+
|
|
7
|
+
jest.mock('./stableVariablesHash', () => ({
|
|
8
|
+
stableVariablesHash: object => JSON.stringify(object)
|
|
9
|
+
}));
|
|
10
|
+
const testVariables = {
|
|
11
|
+
question: 'What do you get when you multiply six by nine?',
|
|
12
|
+
answer: 42
|
|
13
|
+
};
|
|
14
|
+
const testHash = (0, _stableVariablesHash.stableVariablesHash)(testVariables);
|
|
15
|
+
describe('mergeAndCompareVariables', () => {
|
|
16
|
+
it('Should return previous variables and hash when no new variables are provided', () => {
|
|
17
|
+
expect((0, _mergeAndCompareVariables.mergeAndCompareVariables)(testVariables, undefined, undefined)).toMatchObject({
|
|
18
|
+
identical: true,
|
|
19
|
+
mergedVariables: testVariables,
|
|
20
|
+
mergedVariablesHash: undefined
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
it('Should return identical: true when merged variables are identical to old variables (without prev hash)', () => {
|
|
24
|
+
const newVariables = {
|
|
25
|
+
answer: testVariables.answer
|
|
26
|
+
};
|
|
27
|
+
expect((0, _mergeAndCompareVariables.mergeAndCompareVariables)(testVariables, newVariables, undefined)).toMatchObject({
|
|
28
|
+
identical: true,
|
|
29
|
+
mergedVariables: testVariables,
|
|
30
|
+
mergedVariablesHash: testHash
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
it('Should return identical: false with incorrect previous hash', () => {
|
|
34
|
+
const incorrectPreviousHash = 'IAmAHash';
|
|
35
|
+
const newVariables = {
|
|
36
|
+
answer: 42
|
|
37
|
+
};
|
|
38
|
+
expect((0, _mergeAndCompareVariables.mergeAndCompareVariables)(testVariables, newVariables, incorrectPreviousHash)).toMatchObject({
|
|
39
|
+
identical: false,
|
|
40
|
+
mergedVariables: testVariables,
|
|
41
|
+
mergedVariablesHash: testHash
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
it('Should return identical: false when merged variables are different than old variables', () => {
|
|
45
|
+
const newVariables = {
|
|
46
|
+
answer: 43
|
|
47
|
+
};
|
|
48
|
+
const expectedMergedVariables = { ...testVariables,
|
|
49
|
+
...newVariables
|
|
50
|
+
};
|
|
51
|
+
expect((0, _mergeAndCompareVariables.mergeAndCompareVariables)(testVariables, newVariables, testHash)).toMatchObject({
|
|
52
|
+
identical: false,
|
|
53
|
+
mergedVariables: expectedMergedVariables,
|
|
54
|
+
mergedVariablesHash: (0, _stableVariablesHash.stableVariablesHash)(expectedMergedVariables)
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -9,7 +9,7 @@ var _react = require("react");
|
|
|
9
9
|
|
|
10
10
|
var _reactQuery = require("react-query");
|
|
11
11
|
|
|
12
|
-
var
|
|
12
|
+
var _mergeAndCompareVariables = require("./mergeAndCompareVariables");
|
|
13
13
|
|
|
14
14
|
var _useDataEngine = require("./useDataEngine");
|
|
15
15
|
|
|
@@ -35,24 +35,35 @@ const useDataQuery = (query, {
|
|
|
35
35
|
variables: initialVariables = {},
|
|
36
36
|
lazy: initialLazy = false
|
|
37
37
|
} = {}) => {
|
|
38
|
-
const variablesHash = (0, _react.useRef)(null);
|
|
39
|
-
const [variables, setVariables] = (0, _react.useState)(initialVariables);
|
|
40
|
-
const [enabled, setEnabled] = (0, _react.useState)(!initialLazy);
|
|
41
38
|
const [staticQuery] = (0, _useStaticInput.useStaticInput)(query, {
|
|
42
39
|
warn: true,
|
|
43
40
|
name: 'query'
|
|
44
41
|
});
|
|
42
|
+
const [variablesUpdateCount, setVariablesUpdateCount] = (0, _react.useState)(0);
|
|
43
|
+
const queryState = (0, _react.useRef)({
|
|
44
|
+
variables: initialVariables,
|
|
45
|
+
variablesHash: undefined,
|
|
46
|
+
enabled: !initialLazy,
|
|
47
|
+
refetchCallback: undefined
|
|
48
|
+
});
|
|
45
49
|
/**
|
|
46
|
-
*
|
|
50
|
+
* Display current query state and refetch count in React DevTools
|
|
47
51
|
*/
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
(0, _react.useDebugValue)({
|
|
54
|
+
variablesUpdateCount,
|
|
55
|
+
enabled: queryState.current.enabled,
|
|
56
|
+
variables: queryState.current.variables
|
|
57
|
+
}, debugValue => JSON.stringify(debugValue));
|
|
58
|
+
/**
|
|
59
|
+
* User callbacks and refetch handling
|
|
60
|
+
*/
|
|
50
61
|
|
|
51
62
|
const onSuccess = data => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
var _queryState$current$r, _queryState$current;
|
|
64
|
+
|
|
65
|
+
(_queryState$current$r = (_queryState$current = queryState.current).refetchCallback) === null || _queryState$current$r === void 0 ? void 0 : _queryState$current$r.call(_queryState$current, data);
|
|
66
|
+
queryState.current.refetchCallback = undefined;
|
|
56
67
|
|
|
57
68
|
if (userOnSuccess) {
|
|
58
69
|
userOnSuccess(data);
|
|
@@ -61,9 +72,7 @@ const useDataQuery = (query, {
|
|
|
61
72
|
|
|
62
73
|
const onError = error => {
|
|
63
74
|
// If we'd want to reject on errors we'd call the cb with the error here
|
|
64
|
-
|
|
65
|
-
refetchCallback.current = null;
|
|
66
|
-
}
|
|
75
|
+
queryState.current.refetchCallback = undefined;
|
|
67
76
|
|
|
68
77
|
if (userOnError) {
|
|
69
78
|
userOnError(error);
|
|
@@ -75,10 +84,10 @@ const useDataQuery = (query, {
|
|
|
75
84
|
|
|
76
85
|
|
|
77
86
|
const engine = (0, _useDataEngine.useDataEngine)();
|
|
78
|
-
const queryKey = [staticQuery, variables];
|
|
87
|
+
const queryKey = [staticQuery, queryState.current.variables];
|
|
79
88
|
|
|
80
89
|
const queryFn = () => engine.query(staticQuery, {
|
|
81
|
-
variables
|
|
90
|
+
variables: queryState.current.variables
|
|
82
91
|
});
|
|
83
92
|
|
|
84
93
|
const {
|
|
@@ -89,7 +98,7 @@ const useDataQuery = (query, {
|
|
|
89
98
|
data,
|
|
90
99
|
refetch: queryRefetch
|
|
91
100
|
} = (0, _reactQuery.useQuery)(queryKey, queryFn, {
|
|
92
|
-
enabled,
|
|
101
|
+
enabled: queryState.current.enabled,
|
|
93
102
|
onSuccess,
|
|
94
103
|
onError
|
|
95
104
|
});
|
|
@@ -103,11 +112,17 @@ const useDataQuery = (query, {
|
|
|
103
112
|
*/
|
|
104
113
|
|
|
105
114
|
const refetch = (0, _react.useCallback)(newVariables => {
|
|
115
|
+
const {
|
|
116
|
+
identical,
|
|
117
|
+
mergedVariables,
|
|
118
|
+
mergedVariablesHash
|
|
119
|
+
} = (0, _mergeAndCompareVariables.mergeAndCompareVariables)(queryState.current.variables, newVariables, queryState.current.variablesHash);
|
|
106
120
|
/**
|
|
107
121
|
* If there are no updates that will trigger an automatic refetch
|
|
108
122
|
* we'll need to call react-query's refetch directly
|
|
109
123
|
*/
|
|
110
|
-
|
|
124
|
+
|
|
125
|
+
if (queryState.current.enabled && identical) {
|
|
111
126
|
return queryRefetch({
|
|
112
127
|
cancelRefetch: true,
|
|
113
128
|
throwOnError: false
|
|
@@ -116,44 +131,19 @@ const useDataQuery = (query, {
|
|
|
116
131
|
}) => data);
|
|
117
132
|
}
|
|
118
133
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const mergedHash = (0, _stableVariablesHash.stableVariablesHash)(mergedVariables);
|
|
126
|
-
const identical = currentHash === mergedHash;
|
|
127
|
-
|
|
128
|
-
if (identical && enabled) {
|
|
129
|
-
/**
|
|
130
|
-
* If the variables are identical and the query is enabled
|
|
131
|
-
* we'll need to trigger the refetch manually
|
|
132
|
-
*/
|
|
133
|
-
return queryRefetch({
|
|
134
|
-
cancelRefetch: true,
|
|
135
|
-
throwOnError: false
|
|
136
|
-
}).then(({
|
|
137
|
-
data
|
|
138
|
-
}) => data);
|
|
139
|
-
} else {
|
|
140
|
-
variablesHash.current = mergedHash;
|
|
141
|
-
setVariables(mergedVariables);
|
|
142
|
-
}
|
|
143
|
-
} // Enable the query after the variables have been set to prevent extra request
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (!enabled) {
|
|
147
|
-
setEnabled(true);
|
|
148
|
-
} // This promise does not currently reject on errors
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return new Promise(resolve => {
|
|
152
|
-
refetchCallback.current = data => {
|
|
134
|
+
queryState.current.variables = mergedVariables;
|
|
135
|
+
queryState.current.variablesHash = mergedVariablesHash;
|
|
136
|
+
queryState.current.enabled = true; // This promise does not currently reject on errors
|
|
137
|
+
|
|
138
|
+
const refetchPromise = new Promise(resolve => {
|
|
139
|
+
queryState.current.refetchCallback = data => {
|
|
153
140
|
resolve(data);
|
|
154
141
|
};
|
|
155
|
-
});
|
|
156
|
-
|
|
142
|
+
}); // Trigger a react-query refetch by incrementing variablesUpdateCount state
|
|
143
|
+
|
|
144
|
+
setVariablesUpdateCount(prevCount => prevCount + 1);
|
|
145
|
+
return refetchPromise;
|
|
146
|
+
}, [queryRefetch]);
|
|
157
147
|
/**
|
|
158
148
|
* react-query returns null or an error, but we return undefined
|
|
159
149
|
* or an error, so this ensures consistency with the other types.
|
|
@@ -507,6 +507,65 @@ describe('useDataQuery', () => {
|
|
|
507
507
|
});
|
|
508
508
|
});
|
|
509
509
|
describe('return values: refetch', () => {
|
|
510
|
+
it('Should be stable if the query variables change', async () => {
|
|
511
|
+
let count = 0;
|
|
512
|
+
const spy = jest.fn(() => {
|
|
513
|
+
count++;
|
|
514
|
+
return count;
|
|
515
|
+
});
|
|
516
|
+
const data = {
|
|
517
|
+
answer: spy
|
|
518
|
+
};
|
|
519
|
+
const query = {
|
|
520
|
+
x: {
|
|
521
|
+
resource: 'answer'
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
const wrapper = ({
|
|
526
|
+
children
|
|
527
|
+
}) => /*#__PURE__*/React.createElement(_CustomDataProvider.CustomDataProvider, {
|
|
528
|
+
data: data
|
|
529
|
+
}, children);
|
|
530
|
+
|
|
531
|
+
const {
|
|
532
|
+
result,
|
|
533
|
+
waitFor
|
|
534
|
+
} = (0, _reactHooks.renderHook)(() => (0, _useDataQuery.useDataQuery)(query, {
|
|
535
|
+
lazy: true
|
|
536
|
+
}), {
|
|
537
|
+
wrapper
|
|
538
|
+
});
|
|
539
|
+
expect(spy).not.toHaveBeenCalled();
|
|
540
|
+
const initialRefetch = result.current.refetch;
|
|
541
|
+
(0, _reactHooks.act)(() => {
|
|
542
|
+
initialRefetch();
|
|
543
|
+
});
|
|
544
|
+
await waitFor(() => {
|
|
545
|
+
expect(result.current).toMatchObject({
|
|
546
|
+
loading: false,
|
|
547
|
+
called: true,
|
|
548
|
+
data: {
|
|
549
|
+
x: 1
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
554
|
+
(0, _reactHooks.act)(() => {
|
|
555
|
+
initialRefetch();
|
|
556
|
+
});
|
|
557
|
+
await waitFor(() => {
|
|
558
|
+
expect(result.current).toMatchObject({
|
|
559
|
+
loading: false,
|
|
560
|
+
called: true,
|
|
561
|
+
data: {
|
|
562
|
+
x: 2
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
expect(spy).toHaveBeenCalledTimes(2);
|
|
567
|
+
expect(initialRefetch).toBe(result.current.refetch);
|
|
568
|
+
});
|
|
510
569
|
it('Should only trigger a single request when refetch is called on a lazy query with new variables', async () => {
|
|
511
570
|
const spy = jest.fn((type, query) => {
|
|
512
571
|
if (query.id === '1') {
|
|
@@ -13,6 +13,7 @@ const useStaticInput = (staticValue, {
|
|
|
13
13
|
} = {}) => {
|
|
14
14
|
const originalValue = (0, _react.useRef)(staticValue);
|
|
15
15
|
const [value, setValue] = (0, _react.useState)(() => originalValue.current);
|
|
16
|
+
(0, _react.useDebugValue)(value, debugValue => "".concat(name, ": ").concat(JSON.stringify(debugValue)));
|
|
16
17
|
(0, _react.useEffect)(() => {
|
|
17
18
|
if (warn && originalValue.current !== staticValue) {
|
|
18
19
|
console.warn("The ".concat(name, " should be static, don't create it within the render loop!"));
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { stableVariablesHash } from './stableVariablesHash';
|
|
2
|
+
export const mergeAndCompareVariables = (previousVariables, newVariables, previousHash) => {
|
|
3
|
+
if (!newVariables) {
|
|
4
|
+
return {
|
|
5
|
+
identical: true,
|
|
6
|
+
mergedVariablesHash: previousHash,
|
|
7
|
+
mergedVariables: previousVariables
|
|
8
|
+
};
|
|
9
|
+
} // Use cached hash if it exists
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const currentHash = previousHash || stableVariablesHash(previousVariables);
|
|
13
|
+
const mergedVariables = { ...previousVariables,
|
|
14
|
+
...newVariables
|
|
15
|
+
};
|
|
16
|
+
const mergedVariablesHash = stableVariablesHash(mergedVariables);
|
|
17
|
+
const identical = currentHash === mergedVariablesHash;
|
|
18
|
+
return {
|
|
19
|
+
identical,
|
|
20
|
+
mergedVariablesHash,
|
|
21
|
+
mergedVariables
|
|
22
|
+
};
|
|
23
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { mergeAndCompareVariables } from './mergeAndCompareVariables';
|
|
2
|
+
import { stableVariablesHash } from './stableVariablesHash';
|
|
3
|
+
jest.mock('./stableVariablesHash', () => ({
|
|
4
|
+
stableVariablesHash: object => JSON.stringify(object)
|
|
5
|
+
}));
|
|
6
|
+
const testVariables = {
|
|
7
|
+
question: 'What do you get when you multiply six by nine?',
|
|
8
|
+
answer: 42
|
|
9
|
+
};
|
|
10
|
+
const testHash = stableVariablesHash(testVariables);
|
|
11
|
+
describe('mergeAndCompareVariables', () => {
|
|
12
|
+
it('Should return previous variables and hash when no new variables are provided', () => {
|
|
13
|
+
expect(mergeAndCompareVariables(testVariables, undefined, undefined)).toMatchObject({
|
|
14
|
+
identical: true,
|
|
15
|
+
mergedVariables: testVariables,
|
|
16
|
+
mergedVariablesHash: undefined
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
it('Should return identical: true when merged variables are identical to old variables (without prev hash)', () => {
|
|
20
|
+
const newVariables = {
|
|
21
|
+
answer: testVariables.answer
|
|
22
|
+
};
|
|
23
|
+
expect(mergeAndCompareVariables(testVariables, newVariables, undefined)).toMatchObject({
|
|
24
|
+
identical: true,
|
|
25
|
+
mergedVariables: testVariables,
|
|
26
|
+
mergedVariablesHash: testHash
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
it('Should return identical: false with incorrect previous hash', () => {
|
|
30
|
+
const incorrectPreviousHash = 'IAmAHash';
|
|
31
|
+
const newVariables = {
|
|
32
|
+
answer: 42
|
|
33
|
+
};
|
|
34
|
+
expect(mergeAndCompareVariables(testVariables, newVariables, incorrectPreviousHash)).toMatchObject({
|
|
35
|
+
identical: false,
|
|
36
|
+
mergedVariables: testVariables,
|
|
37
|
+
mergedVariablesHash: testHash
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it('Should return identical: false when merged variables are different than old variables', () => {
|
|
41
|
+
const newVariables = {
|
|
42
|
+
answer: 43
|
|
43
|
+
};
|
|
44
|
+
const expectedMergedVariables = { ...testVariables,
|
|
45
|
+
...newVariables
|
|
46
|
+
};
|
|
47
|
+
expect(mergeAndCompareVariables(testVariables, newVariables, testHash)).toMatchObject({
|
|
48
|
+
identical: false,
|
|
49
|
+
mergedVariables: expectedMergedVariables,
|
|
50
|
+
mergedVariablesHash: stableVariablesHash(expectedMergedVariables)
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useState, useRef, useCallback } from 'react';
|
|
1
|
+
import { useState, useRef, useCallback, useDebugValue } from 'react';
|
|
2
2
|
import { useQuery, setLogger } from 'react-query';
|
|
3
|
-
import {
|
|
3
|
+
import { mergeAndCompareVariables } from './mergeAndCompareVariables';
|
|
4
4
|
import { useDataEngine } from './useDataEngine';
|
|
5
5
|
import { useStaticInput } from './useStaticInput';
|
|
6
6
|
|
|
@@ -23,24 +23,35 @@ export const useDataQuery = (query, {
|
|
|
23
23
|
variables: initialVariables = {},
|
|
24
24
|
lazy: initialLazy = false
|
|
25
25
|
} = {}) => {
|
|
26
|
-
const variablesHash = useRef(null);
|
|
27
|
-
const [variables, setVariables] = useState(initialVariables);
|
|
28
|
-
const [enabled, setEnabled] = useState(!initialLazy);
|
|
29
26
|
const [staticQuery] = useStaticInput(query, {
|
|
30
27
|
warn: true,
|
|
31
28
|
name: 'query'
|
|
32
29
|
});
|
|
30
|
+
const [variablesUpdateCount, setVariablesUpdateCount] = useState(0);
|
|
31
|
+
const queryState = useRef({
|
|
32
|
+
variables: initialVariables,
|
|
33
|
+
variablesHash: undefined,
|
|
34
|
+
enabled: !initialLazy,
|
|
35
|
+
refetchCallback: undefined
|
|
36
|
+
});
|
|
33
37
|
/**
|
|
34
|
-
*
|
|
38
|
+
* Display current query state and refetch count in React DevTools
|
|
35
39
|
*/
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
useDebugValue({
|
|
42
|
+
variablesUpdateCount,
|
|
43
|
+
enabled: queryState.current.enabled,
|
|
44
|
+
variables: queryState.current.variables
|
|
45
|
+
}, debugValue => JSON.stringify(debugValue));
|
|
46
|
+
/**
|
|
47
|
+
* User callbacks and refetch handling
|
|
48
|
+
*/
|
|
38
49
|
|
|
39
50
|
const onSuccess = data => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
var _queryState$current$r, _queryState$current;
|
|
52
|
+
|
|
53
|
+
(_queryState$current$r = (_queryState$current = queryState.current).refetchCallback) === null || _queryState$current$r === void 0 ? void 0 : _queryState$current$r.call(_queryState$current, data);
|
|
54
|
+
queryState.current.refetchCallback = undefined;
|
|
44
55
|
|
|
45
56
|
if (userOnSuccess) {
|
|
46
57
|
userOnSuccess(data);
|
|
@@ -49,9 +60,7 @@ export const useDataQuery = (query, {
|
|
|
49
60
|
|
|
50
61
|
const onError = error => {
|
|
51
62
|
// If we'd want to reject on errors we'd call the cb with the error here
|
|
52
|
-
|
|
53
|
-
refetchCallback.current = null;
|
|
54
|
-
}
|
|
63
|
+
queryState.current.refetchCallback = undefined;
|
|
55
64
|
|
|
56
65
|
if (userOnError) {
|
|
57
66
|
userOnError(error);
|
|
@@ -63,10 +72,10 @@ export const useDataQuery = (query, {
|
|
|
63
72
|
|
|
64
73
|
|
|
65
74
|
const engine = useDataEngine();
|
|
66
|
-
const queryKey = [staticQuery, variables];
|
|
75
|
+
const queryKey = [staticQuery, queryState.current.variables];
|
|
67
76
|
|
|
68
77
|
const queryFn = () => engine.query(staticQuery, {
|
|
69
|
-
variables
|
|
78
|
+
variables: queryState.current.variables
|
|
70
79
|
});
|
|
71
80
|
|
|
72
81
|
const {
|
|
@@ -77,7 +86,7 @@ export const useDataQuery = (query, {
|
|
|
77
86
|
data,
|
|
78
87
|
refetch: queryRefetch
|
|
79
88
|
} = useQuery(queryKey, queryFn, {
|
|
80
|
-
enabled,
|
|
89
|
+
enabled: queryState.current.enabled,
|
|
81
90
|
onSuccess,
|
|
82
91
|
onError
|
|
83
92
|
});
|
|
@@ -91,11 +100,17 @@ export const useDataQuery = (query, {
|
|
|
91
100
|
*/
|
|
92
101
|
|
|
93
102
|
const refetch = useCallback(newVariables => {
|
|
103
|
+
const {
|
|
104
|
+
identical,
|
|
105
|
+
mergedVariables,
|
|
106
|
+
mergedVariablesHash
|
|
107
|
+
} = mergeAndCompareVariables(queryState.current.variables, newVariables, queryState.current.variablesHash);
|
|
94
108
|
/**
|
|
95
109
|
* If there are no updates that will trigger an automatic refetch
|
|
96
110
|
* we'll need to call react-query's refetch directly
|
|
97
111
|
*/
|
|
98
|
-
|
|
112
|
+
|
|
113
|
+
if (queryState.current.enabled && identical) {
|
|
99
114
|
return queryRefetch({
|
|
100
115
|
cancelRefetch: true,
|
|
101
116
|
throwOnError: false
|
|
@@ -104,44 +119,19 @@ export const useDataQuery = (query, {
|
|
|
104
119
|
}) => data);
|
|
105
120
|
}
|
|
106
121
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const mergedHash = stableVariablesHash(mergedVariables);
|
|
114
|
-
const identical = currentHash === mergedHash;
|
|
115
|
-
|
|
116
|
-
if (identical && enabled) {
|
|
117
|
-
/**
|
|
118
|
-
* If the variables are identical and the query is enabled
|
|
119
|
-
* we'll need to trigger the refetch manually
|
|
120
|
-
*/
|
|
121
|
-
return queryRefetch({
|
|
122
|
-
cancelRefetch: true,
|
|
123
|
-
throwOnError: false
|
|
124
|
-
}).then(({
|
|
125
|
-
data
|
|
126
|
-
}) => data);
|
|
127
|
-
} else {
|
|
128
|
-
variablesHash.current = mergedHash;
|
|
129
|
-
setVariables(mergedVariables);
|
|
130
|
-
}
|
|
131
|
-
} // Enable the query after the variables have been set to prevent extra request
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (!enabled) {
|
|
135
|
-
setEnabled(true);
|
|
136
|
-
} // This promise does not currently reject on errors
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return new Promise(resolve => {
|
|
140
|
-
refetchCallback.current = data => {
|
|
122
|
+
queryState.current.variables = mergedVariables;
|
|
123
|
+
queryState.current.variablesHash = mergedVariablesHash;
|
|
124
|
+
queryState.current.enabled = true; // This promise does not currently reject on errors
|
|
125
|
+
|
|
126
|
+
const refetchPromise = new Promise(resolve => {
|
|
127
|
+
queryState.current.refetchCallback = data => {
|
|
141
128
|
resolve(data);
|
|
142
129
|
};
|
|
143
|
-
});
|
|
144
|
-
|
|
130
|
+
}); // Trigger a react-query refetch by incrementing variablesUpdateCount state
|
|
131
|
+
|
|
132
|
+
setVariablesUpdateCount(prevCount => prevCount + 1);
|
|
133
|
+
return refetchPromise;
|
|
134
|
+
}, [queryRefetch]);
|
|
145
135
|
/**
|
|
146
136
|
* react-query returns null or an error, but we return undefined
|
|
147
137
|
* or an error, so this ensures consistency with the other types.
|
|
@@ -497,6 +497,65 @@ describe('useDataQuery', () => {
|
|
|
497
497
|
});
|
|
498
498
|
});
|
|
499
499
|
describe('return values: refetch', () => {
|
|
500
|
+
it('Should be stable if the query variables change', async () => {
|
|
501
|
+
let count = 0;
|
|
502
|
+
const spy = jest.fn(() => {
|
|
503
|
+
count++;
|
|
504
|
+
return count;
|
|
505
|
+
});
|
|
506
|
+
const data = {
|
|
507
|
+
answer: spy
|
|
508
|
+
};
|
|
509
|
+
const query = {
|
|
510
|
+
x: {
|
|
511
|
+
resource: 'answer'
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const wrapper = ({
|
|
516
|
+
children
|
|
517
|
+
}) => /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
518
|
+
data: data
|
|
519
|
+
}, children);
|
|
520
|
+
|
|
521
|
+
const {
|
|
522
|
+
result,
|
|
523
|
+
waitFor
|
|
524
|
+
} = renderHook(() => useDataQuery(query, {
|
|
525
|
+
lazy: true
|
|
526
|
+
}), {
|
|
527
|
+
wrapper
|
|
528
|
+
});
|
|
529
|
+
expect(spy).not.toHaveBeenCalled();
|
|
530
|
+
const initialRefetch = result.current.refetch;
|
|
531
|
+
act(() => {
|
|
532
|
+
initialRefetch();
|
|
533
|
+
});
|
|
534
|
+
await waitFor(() => {
|
|
535
|
+
expect(result.current).toMatchObject({
|
|
536
|
+
loading: false,
|
|
537
|
+
called: true,
|
|
538
|
+
data: {
|
|
539
|
+
x: 1
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
544
|
+
act(() => {
|
|
545
|
+
initialRefetch();
|
|
546
|
+
});
|
|
547
|
+
await waitFor(() => {
|
|
548
|
+
expect(result.current).toMatchObject({
|
|
549
|
+
loading: false,
|
|
550
|
+
called: true,
|
|
551
|
+
data: {
|
|
552
|
+
x: 2
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
expect(spy).toHaveBeenCalledTimes(2);
|
|
557
|
+
expect(initialRefetch).toBe(result.current.refetch);
|
|
558
|
+
});
|
|
500
559
|
it('Should only trigger a single request when refetch is called on a lazy query with new variables', async () => {
|
|
501
560
|
const spy = jest.fn((type, query) => {
|
|
502
561
|
if (query.id === '1') {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react';
|
|
1
|
+
import { useState, useEffect, useRef, useDebugValue } from 'react';
|
|
2
2
|
export const useStaticInput = (staticValue, {
|
|
3
3
|
warn = false,
|
|
4
4
|
name = 'input'
|
|
5
5
|
} = {}) => {
|
|
6
6
|
const originalValue = useRef(staticValue);
|
|
7
7
|
const [value, setValue] = useState(() => originalValue.current);
|
|
8
|
+
useDebugValue(value, debugValue => "".concat(name, ": ").concat(JSON.stringify(debugValue)));
|
|
8
9
|
useEffect(() => {
|
|
9
10
|
if (warn && originalValue.current !== staticValue) {
|
|
10
11
|
console.warn("The ".concat(name, " should be static, don't create it within the render loop!"));
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { QueryVariables } from '../../engine';
|
|
2
|
+
export declare const mergeAndCompareVariables: (previousVariables?: QueryVariables | undefined, newVariables?: QueryVariables | undefined, previousHash?: string | undefined) => {
|
|
3
|
+
identical: boolean;
|
|
4
|
+
mergedVariablesHash: string | undefined;
|
|
5
|
+
mergedVariables: QueryVariables | undefined;
|
|
6
|
+
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { Query, QueryOptions } from '../../engine';
|
|
2
|
-
import { QueryRenderInput } from '../../types';
|
|
1
|
+
import type { Query, QueryOptions } from '../../engine';
|
|
2
|
+
import type { QueryRenderInput } from '../../types';
|
|
3
3
|
export declare const useDataQuery: (query: Query, { onComplete: userOnSuccess, onError: userOnError, variables: initialVariables, lazy: initialLazy, }?: QueryOptions) => QueryRenderInput;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhis2/app-service-data",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.2",
|
|
4
4
|
"main": "./build/cjs/index.js",
|
|
5
5
|
"module": "./build/es/index.js",
|
|
6
6
|
"types": "build/types/index.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"build/**"
|
|
23
23
|
],
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@dhis2/app-service-config": "3.4.
|
|
25
|
+
"@dhis2/app-service-config": "3.4.2",
|
|
26
26
|
"@dhis2/cli-app-scripts": "^7.1.1",
|
|
27
27
|
"prop-types": "^15.7.2",
|
|
28
28
|
"react": "^16.8",
|