@khanacademy/wonder-blocks-data 11.0.16 → 13.0.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/CHANGELOG.md +24 -0
- package/dist/components/data.d.ts +2 -2
- package/dist/es/index.js +24 -18
- package/dist/hooks/use-gql.d.ts +5 -1
- package/dist/hooks/use-hydratable-effect.d.ts +6 -6
- package/dist/index.js +24 -18
- package/dist/util/status.d.ts +4 -3
- package/dist/util/types.d.ts +8 -6
- package/package.json +3 -3
- package/src/components/__tests__/data.test.tsx +6 -13
- package/src/components/data.ts +2 -4
- package/src/hooks/__tests__/use-cached-effect.test.tsx +79 -40
- package/src/hooks/__tests__/use-gql-router-context.test.tsx +1 -2
- package/src/hooks/__tests__/use-hydratable-effect.test.ts +1 -2
- package/src/hooks/__tests__/use-request-interception.test.tsx +2 -5
- package/src/hooks/__tests__/use-server-effect.test.ts +3 -6
- package/src/hooks/__tests__/use-shared-cache.test.ts +17 -13
- package/src/hooks/use-cached-effect.ts +24 -20
- package/src/hooks/use-gql.ts +12 -9
- package/src/hooks/use-hydratable-effect.ts +6 -7
- package/src/hooks/use-request-interception.ts +13 -11
- package/src/hooks/use-shared-cache.ts +4 -2
- package/src/util/__tests__/request-api.test.ts +2 -1
- package/src/util/__tests__/request-tracking.test.tsx +5 -9
- package/src/util/__tests__/result-from-cache-response.test.ts +2 -2
- package/src/util/__tests__/serializable-in-memory-cache.test.ts +1 -2
- package/src/util/__tests__/ssr-cache.test.ts +2 -4
- package/src/util/__tests__/to-gql-operation.test.ts +2 -4
- package/src/util/graphql-document-node-parser.ts +6 -6
- package/src/util/merge-gql-context.ts +2 -1
- package/src/util/request-tracking.ts +6 -2
- package/src/util/ssr-cache.ts +11 -8
- package/src/util/status.ts +6 -0
- package/src/util/types.ts +9 -7
- package/tsconfig-build.tsbuildinfo +1 -1
- package/dist/components/data.js.flow +0 -63
- package/dist/components/gql-router.js.flow +0 -33
- package/dist/components/intercept-context.js.flow +0 -18
- package/dist/components/intercept-requests.js.flow +0 -51
- package/dist/components/track-data.js.flow +0 -16
- package/dist/hooks/use-cached-effect.js.flow +0 -83
- package/dist/hooks/use-gql-router-context.js.flow +0 -14
- package/dist/hooks/use-gql.js.flow +0 -28
- package/dist/hooks/use-hydratable-effect.js.flow +0 -122
- package/dist/hooks/use-request-interception.js.flow +0 -24
- package/dist/hooks/use-server-effect.js.flow +0 -49
- package/dist/hooks/use-shared-cache.js.flow +0 -42
- package/dist/index.js.flow +0 -47
- package/dist/util/data-error.js.flow +0 -62
- package/dist/util/get-gql-data-from-response.js.flow +0 -12
- package/dist/util/get-gql-request-id.js.flow +0 -16
- package/dist/util/gql-error.js.flow +0 -41
- package/dist/util/gql-router-context.js.flow +0 -10
- package/dist/util/gql-types.js.flow +0 -50
- package/dist/util/graphql-document-node-parser.js.flow +0 -29
- package/dist/util/graphql-types.js.flow +0 -30
- package/dist/util/hydration-cache-api.js.flow +0 -29
- package/dist/util/merge-gql-context.js.flow +0 -18
- package/dist/util/purge-caches.js.flow +0 -14
- package/dist/util/request-api.js.flow +0 -33
- package/dist/util/request-fulfillment.js.flow +0 -48
- package/dist/util/request-tracking.js.flow +0 -81
- package/dist/util/result-from-cache-response.js.flow +0 -14
- package/dist/util/scoped-in-memory-cache.js.flow +0 -56
- package/dist/util/serializable-in-memory-cache.js.flow +0 -25
- package/dist/util/ssr-cache.js.flow +0 -86
- package/dist/util/status.js.flow +0 -17
- package/dist/util/to-gql-operation.js.flow +0 -41
- package/dist/util/types.js.flow +0 -142
- package/src/util/graphql-types.js.flow +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-data
|
|
2
2
|
|
|
3
|
+
## 13.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 65c02cff: Return new no-data state from useCachedEffect when it is not loading and there is no data to return
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [1920feb8]
|
|
12
|
+
- @khanacademy/wonder-blocks-core@6.0.1
|
|
13
|
+
|
|
14
|
+
## 12.0.0
|
|
15
|
+
|
|
16
|
+
### Major Changes
|
|
17
|
+
|
|
18
|
+
- 674a1e5c: POJOs have been replace with TS enums
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- 674a1e5c: We're no longer building flow types
|
|
23
|
+
- Updated dependencies [674a1e5c]
|
|
24
|
+
- Updated dependencies [674a1e5c]
|
|
25
|
+
- @khanacademy/wonder-blocks-core@6.0.0
|
|
26
|
+
|
|
3
27
|
## 11.0.16
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
|
@@ -41,12 +41,12 @@ TData extends ValidCacheData> = {
|
|
|
41
41
|
* loading state and data or error that gets retrieved from cache or loaded
|
|
42
42
|
* via the request if no cached value is available.
|
|
43
43
|
*/
|
|
44
|
-
children: (result: Result<TData>) => React.
|
|
44
|
+
children: (result: Result<TData>) => React.ReactElement | null;
|
|
45
45
|
};
|
|
46
46
|
/**
|
|
47
47
|
* This component is the main component of Wonder Blocks Data. With this, data
|
|
48
48
|
* requirements can be placed in a React application in a manner that will
|
|
49
49
|
* support server-side rendering and efficient caching.
|
|
50
50
|
*/
|
|
51
|
-
declare const Data: <TData extends ValidCacheData>({ requestId, handler, children, retainResultOnChange, clientBehavior, }: Props<TData>) => React.ReactElement;
|
|
51
|
+
declare const Data: <TData extends ValidCacheData>({ requestId, handler, children, retainResultOnChange, clientBehavior, }: Props<TData>) => React.ReactElement | null;
|
|
52
52
|
export default Data;
|
package/dist/es/index.js
CHANGED
|
@@ -3,12 +3,13 @@ import { KindError, clone, entries } from '@khanacademy/wonder-stuff-core';
|
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { useContext, useRef, useMemo, useCallback } from 'react';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
CacheBeforeNetwork
|
|
8
|
-
CacheAndNetwork
|
|
9
|
-
CacheOnly
|
|
10
|
-
NetworkOnly
|
|
11
|
-
|
|
6
|
+
let FetchPolicy = function (FetchPolicy) {
|
|
7
|
+
FetchPolicy["CacheBeforeNetwork"] = "CacheBeforeNetwork";
|
|
8
|
+
FetchPolicy["CacheAndNetwork"] = "CacheAndNetwork";
|
|
9
|
+
FetchPolicy["CacheOnly"] = "CacheOnly";
|
|
10
|
+
FetchPolicy["NetworkOnly"] = "NetworkOnly";
|
|
11
|
+
return FetchPolicy;
|
|
12
|
+
}({});
|
|
12
13
|
|
|
13
14
|
const DataErrors = Object.freeze({
|
|
14
15
|
Unknown: "Unknown",
|
|
@@ -161,9 +162,9 @@ class SsrCache {
|
|
|
161
162
|
this._ssrOnlyCache.purgeAll(realPredicate);
|
|
162
163
|
};
|
|
163
164
|
this.cloneHydratableData = () => {
|
|
164
|
-
var
|
|
165
|
+
var _ref;
|
|
165
166
|
const cache = this._hydrationCache.clone();
|
|
166
|
-
return (
|
|
167
|
+
return (_ref = cache[DefaultScope$2]) != null ? _ref : {};
|
|
167
168
|
};
|
|
168
169
|
this._ssrOnlyCache = ssrOnlyCache || new SerializableInMemoryCache();
|
|
169
170
|
this._hydrationCache = hydrationCache || new SerializableInMemoryCache();
|
|
@@ -260,7 +261,7 @@ class RequestTracker {
|
|
|
260
261
|
}
|
|
261
262
|
return _default;
|
|
262
263
|
}
|
|
263
|
-
constructor(responseCache
|
|
264
|
+
constructor(responseCache) {
|
|
264
265
|
this._trackedRequests = {};
|
|
265
266
|
this._responseCache = void 0;
|
|
266
267
|
this._requestFulfillment = void 0;
|
|
@@ -378,11 +379,15 @@ class TrackData extends React.Component {
|
|
|
378
379
|
const loadingStatus = Object.freeze({
|
|
379
380
|
status: "loading"
|
|
380
381
|
});
|
|
382
|
+
const noDataStatus = Object.freeze({
|
|
383
|
+
status: "no-data"
|
|
384
|
+
});
|
|
381
385
|
const abortedStatus = Object.freeze({
|
|
382
386
|
status: "aborted"
|
|
383
387
|
});
|
|
384
388
|
const Status = Object.freeze({
|
|
385
389
|
loading: () => loadingStatus,
|
|
390
|
+
noData: () => noDataStatus,
|
|
386
391
|
aborted: () => abortedStatus,
|
|
387
392
|
success: data => ({
|
|
388
393
|
status: "success",
|
|
@@ -526,19 +531,20 @@ const useCachedEffect = (requestId, handler, options = {}) => {
|
|
|
526
531
|
currentRequestRef.current = null;
|
|
527
532
|
};
|
|
528
533
|
}, [shouldFetch, fetchRequest]);
|
|
529
|
-
const lastResultAgnosticOfIdRef = React.useRef(Status.
|
|
530
|
-
const loadingResult = retainResultOnChange ? lastResultAgnosticOfIdRef.current : Status.loading();
|
|
534
|
+
const lastResultAgnosticOfIdRef = React.useRef(Status.noData());
|
|
535
|
+
const loadingResult = retainResultOnChange ? lastResultAgnosticOfIdRef.current : shouldFetch ? Status.loading() : Status.noData();
|
|
531
536
|
const result = (_ref = fetchPolicy === FetchPolicy.NetworkOnly ? networkResultRef.current : mostRecentResult) != null ? _ref : loadingResult;
|
|
532
537
|
lastResultAgnosticOfIdRef.current = result;
|
|
533
538
|
return [result, fetchRequest];
|
|
534
539
|
};
|
|
535
540
|
|
|
536
|
-
|
|
537
|
-
DoNotHydrate
|
|
538
|
-
ExecuteWhenNoResult
|
|
539
|
-
ExecuteWhenNoSuccessResult
|
|
540
|
-
AlwaysExecute
|
|
541
|
-
|
|
541
|
+
let WhenClientSide = function (WhenClientSide) {
|
|
542
|
+
WhenClientSide["DoNotHydrate"] = "DoNotHydrate";
|
|
543
|
+
WhenClientSide["ExecuteWhenNoResult"] = "ExecuteWhenNoResult";
|
|
544
|
+
WhenClientSide["ExecuteWhenNoSuccessResult"] = "ExecuteWhenNoSuccessResult";
|
|
545
|
+
WhenClientSide["AlwaysExecute"] = "AlwaysExecute";
|
|
546
|
+
return WhenClientSide;
|
|
547
|
+
}({});
|
|
542
548
|
const DefaultScope = "useHydratableEffect";
|
|
543
549
|
const useHydratableEffect = (requestId, handler, options = {}) => {
|
|
544
550
|
const {
|
|
@@ -839,7 +845,7 @@ const useGql = (context = {}) => {
|
|
|
839
845
|
context = {}
|
|
840
846
|
} = options;
|
|
841
847
|
const finalContext = mergeGqlContext(defaultContext, context);
|
|
842
|
-
return fetch(operation, variables, finalContext).then(getGqlDataFromResponse);
|
|
848
|
+
return fetch(operation, variables, finalContext).then(response => getGqlDataFromResponse(response));
|
|
843
849
|
}, [gqlRouterContext]);
|
|
844
850
|
return gqlFetch;
|
|
845
851
|
};
|
package/dist/hooks/use-gql.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { GqlContext, GqlOperation, GqlFetchOptions } from "../util/gql-types";
|
|
2
|
+
interface GqlFetchFn<TContext extends GqlContext> {
|
|
3
|
+
<TData, TVariables extends Record<any, any>>(operation: GqlOperation<TData, TVariables>, options?: GqlFetchOptions<TVariables, TContext>): Promise<TData>;
|
|
4
|
+
}
|
|
2
5
|
/**
|
|
3
6
|
* Hook to obtain a gqlFetch function for performing GraphQL requests.
|
|
4
7
|
*
|
|
@@ -9,4 +12,5 @@ import type { GqlContext, GqlOperation, GqlFetchOptions } from "../util/gql-type
|
|
|
9
12
|
* Values in the partial context given to the returned fetch function will
|
|
10
13
|
* only be included if they have a value other than undefined.
|
|
11
14
|
*/
|
|
12
|
-
export declare const useGql: <TContext extends GqlContext>(context?: Partial<TContext>) => <
|
|
15
|
+
export declare const useGql: <TContext extends GqlContext>(context?: Partial<TContext>) => GqlFetchFn<TContext>;
|
|
16
|
+
export {};
|
|
@@ -2,7 +2,7 @@ import type { Result, ValidCacheData } from "../util/types";
|
|
|
2
2
|
/**
|
|
3
3
|
* Policies to define how a hydratable effect should behave client-side.
|
|
4
4
|
*/
|
|
5
|
-
export declare
|
|
5
|
+
export declare enum WhenClientSide {
|
|
6
6
|
/**
|
|
7
7
|
* The result from executing the effect server-side will not be hydrated.
|
|
8
8
|
* The effect will always be executed client-side.
|
|
@@ -11,14 +11,14 @@ export declare const WhenClientSide: {
|
|
|
11
11
|
* for properly hydrating this component (for example, the action invokes
|
|
12
12
|
* Apollo which manages its own cache to ensure things render properly).
|
|
13
13
|
*/
|
|
14
|
-
|
|
14
|
+
DoNotHydrate = "DoNotHydrate",
|
|
15
15
|
/**
|
|
16
16
|
* The result from executing the effect server-side will be hydrated.
|
|
17
17
|
* The effect will only execute client-side if there was no result to
|
|
18
18
|
* be hydrated (i.e. both error and success hydration results prevent the
|
|
19
19
|
* effect running client-side).
|
|
20
20
|
*/
|
|
21
|
-
|
|
21
|
+
ExecuteWhenNoResult = "ExecuteWhenNoResult",
|
|
22
22
|
/**
|
|
23
23
|
* The result from executing the effect server-side will be hydrated.
|
|
24
24
|
* If the hydrated result is a success result, the effect will not be
|
|
@@ -26,14 +26,14 @@ export declare const WhenClientSide: {
|
|
|
26
26
|
* If the hydrated result was not a success result, or there was no
|
|
27
27
|
* hydrated result, the effect will not be executed.
|
|
28
28
|
*/
|
|
29
|
-
|
|
29
|
+
ExecuteWhenNoSuccessResult = "ExecuteWhenNoSuccessResult",
|
|
30
30
|
/**
|
|
31
31
|
* The result from executing the effect server-side will be hydrated.
|
|
32
32
|
* The effect will always be executed client-side, regardless of the
|
|
33
33
|
* hydrated result status.
|
|
34
34
|
*/
|
|
35
|
-
|
|
36
|
-
}
|
|
35
|
+
AlwaysExecute = "AlwaysExecute"
|
|
36
|
+
}
|
|
37
37
|
type HydratableEffectOptions<TData extends ValidCacheData> = {
|
|
38
38
|
/**
|
|
39
39
|
* How the hook should behave when rendering client-side for the first time.
|
package/dist/index.js
CHANGED
|
@@ -26,12 +26,13 @@ function _interopNamespace(e) {
|
|
|
26
26
|
|
|
27
27
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
CacheBeforeNetwork
|
|
31
|
-
CacheAndNetwork
|
|
32
|
-
CacheOnly
|
|
33
|
-
NetworkOnly
|
|
34
|
-
|
|
29
|
+
let FetchPolicy = function (FetchPolicy) {
|
|
30
|
+
FetchPolicy["CacheBeforeNetwork"] = "CacheBeforeNetwork";
|
|
31
|
+
FetchPolicy["CacheAndNetwork"] = "CacheAndNetwork";
|
|
32
|
+
FetchPolicy["CacheOnly"] = "CacheOnly";
|
|
33
|
+
FetchPolicy["NetworkOnly"] = "NetworkOnly";
|
|
34
|
+
return FetchPolicy;
|
|
35
|
+
}({});
|
|
35
36
|
|
|
36
37
|
const DataErrors = Object.freeze({
|
|
37
38
|
Unknown: "Unknown",
|
|
@@ -184,9 +185,9 @@ class SsrCache {
|
|
|
184
185
|
this._ssrOnlyCache.purgeAll(realPredicate);
|
|
185
186
|
};
|
|
186
187
|
this.cloneHydratableData = () => {
|
|
187
|
-
var
|
|
188
|
+
var _ref;
|
|
188
189
|
const cache = this._hydrationCache.clone();
|
|
189
|
-
return (
|
|
190
|
+
return (_ref = cache[DefaultScope$2]) != null ? _ref : {};
|
|
190
191
|
};
|
|
191
192
|
this._ssrOnlyCache = ssrOnlyCache || new SerializableInMemoryCache();
|
|
192
193
|
this._hydrationCache = hydrationCache || new SerializableInMemoryCache();
|
|
@@ -283,7 +284,7 @@ class RequestTracker {
|
|
|
283
284
|
}
|
|
284
285
|
return _default;
|
|
285
286
|
}
|
|
286
|
-
constructor(responseCache
|
|
287
|
+
constructor(responseCache) {
|
|
287
288
|
this._trackedRequests = {};
|
|
288
289
|
this._responseCache = void 0;
|
|
289
290
|
this._requestFulfillment = void 0;
|
|
@@ -401,11 +402,15 @@ class TrackData extends React__namespace.Component {
|
|
|
401
402
|
const loadingStatus = Object.freeze({
|
|
402
403
|
status: "loading"
|
|
403
404
|
});
|
|
405
|
+
const noDataStatus = Object.freeze({
|
|
406
|
+
status: "no-data"
|
|
407
|
+
});
|
|
404
408
|
const abortedStatus = Object.freeze({
|
|
405
409
|
status: "aborted"
|
|
406
410
|
});
|
|
407
411
|
const Status = Object.freeze({
|
|
408
412
|
loading: () => loadingStatus,
|
|
413
|
+
noData: () => noDataStatus,
|
|
409
414
|
aborted: () => abortedStatus,
|
|
410
415
|
success: data => ({
|
|
411
416
|
status: "success",
|
|
@@ -549,19 +554,20 @@ const useCachedEffect = (requestId, handler, options = {}) => {
|
|
|
549
554
|
currentRequestRef.current = null;
|
|
550
555
|
};
|
|
551
556
|
}, [shouldFetch, fetchRequest]);
|
|
552
|
-
const lastResultAgnosticOfIdRef = React__namespace.useRef(Status.
|
|
553
|
-
const loadingResult = retainResultOnChange ? lastResultAgnosticOfIdRef.current : Status.loading();
|
|
557
|
+
const lastResultAgnosticOfIdRef = React__namespace.useRef(Status.noData());
|
|
558
|
+
const loadingResult = retainResultOnChange ? lastResultAgnosticOfIdRef.current : shouldFetch ? Status.loading() : Status.noData();
|
|
554
559
|
const result = (_ref = fetchPolicy === FetchPolicy.NetworkOnly ? networkResultRef.current : mostRecentResult) != null ? _ref : loadingResult;
|
|
555
560
|
lastResultAgnosticOfIdRef.current = result;
|
|
556
561
|
return [result, fetchRequest];
|
|
557
562
|
};
|
|
558
563
|
|
|
559
|
-
|
|
560
|
-
DoNotHydrate
|
|
561
|
-
ExecuteWhenNoResult
|
|
562
|
-
ExecuteWhenNoSuccessResult
|
|
563
|
-
AlwaysExecute
|
|
564
|
-
|
|
564
|
+
let WhenClientSide = function (WhenClientSide) {
|
|
565
|
+
WhenClientSide["DoNotHydrate"] = "DoNotHydrate";
|
|
566
|
+
WhenClientSide["ExecuteWhenNoResult"] = "ExecuteWhenNoResult";
|
|
567
|
+
WhenClientSide["ExecuteWhenNoSuccessResult"] = "ExecuteWhenNoSuccessResult";
|
|
568
|
+
WhenClientSide["AlwaysExecute"] = "AlwaysExecute";
|
|
569
|
+
return WhenClientSide;
|
|
570
|
+
}({});
|
|
565
571
|
const DefaultScope = "useHydratableEffect";
|
|
566
572
|
const useHydratableEffect = (requestId, handler, options = {}) => {
|
|
567
573
|
const {
|
|
@@ -862,7 +868,7 @@ const useGql = (context = {}) => {
|
|
|
862
868
|
context = {}
|
|
863
869
|
} = options;
|
|
864
870
|
const finalContext = mergeGqlContext(defaultContext, context);
|
|
865
|
-
return fetch(operation, variables, finalContext).then(getGqlDataFromResponse);
|
|
871
|
+
return fetch(operation, variables, finalContext).then(response => getGqlDataFromResponse(response));
|
|
866
872
|
}, [gqlRouterContext]);
|
|
867
873
|
return gqlFetch;
|
|
868
874
|
};
|
package/dist/util/status.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ import type { Result, ValidCacheData } from "./types";
|
|
|
4
4
|
*/
|
|
5
5
|
export declare const Status: Readonly<{
|
|
6
6
|
loading: <TData extends ValidCacheData = ValidCacheData>() => Result<TData>;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
noData: <TData_1 extends ValidCacheData = ValidCacheData>() => Result<TData_1>;
|
|
8
|
+
aborted: <TData_2 extends ValidCacheData = ValidCacheData>() => Result<TData_2>;
|
|
9
|
+
success: <TData_3 extends ValidCacheData>(data: TData_3) => Result<TData_3>;
|
|
10
|
+
error: <TData_4 extends ValidCacheData = ValidCacheData>(error: Error) => Result<TData_4>;
|
|
10
11
|
}>;
|
package/dist/util/types.d.ts
CHANGED
|
@@ -2,26 +2,26 @@ import type { Metadata } from "@khanacademy/wonder-stuff-core";
|
|
|
2
2
|
/**
|
|
3
3
|
* Defines the various fetch policies that can be applied to requests.
|
|
4
4
|
*/
|
|
5
|
-
export declare
|
|
5
|
+
export declare enum FetchPolicy {
|
|
6
6
|
/**
|
|
7
7
|
* If the data is in the cache, return that; otherwise, fetch from the
|
|
8
8
|
* server.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
CacheBeforeNetwork = "CacheBeforeNetwork",
|
|
11
11
|
/**
|
|
12
12
|
* If the data is in the cache, return that; always fetch from the server
|
|
13
13
|
* regardless of cache.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
CacheAndNetwork = "CacheAndNetwork",
|
|
16
16
|
/**
|
|
17
17
|
* If the data is in the cache, return that; otherwise, do nothing.
|
|
18
18
|
*/
|
|
19
|
-
|
|
19
|
+
CacheOnly = "CacheOnly",
|
|
20
20
|
/**
|
|
21
21
|
* Ignore any existing cached result; always fetch from the server.
|
|
22
22
|
*/
|
|
23
|
-
|
|
24
|
-
}
|
|
23
|
+
NetworkOnly = "NetworkOnly"
|
|
24
|
+
}
|
|
25
25
|
/**
|
|
26
26
|
* Define what can be cached.
|
|
27
27
|
*
|
|
@@ -34,6 +34,8 @@ export type ValidCacheData = string | boolean | number | Record<any, any> | Arra
|
|
|
34
34
|
*/
|
|
35
35
|
export type Result<TData extends ValidCacheData> = {
|
|
36
36
|
status: "loading";
|
|
37
|
+
} | {
|
|
38
|
+
status: "no-data";
|
|
37
39
|
} | {
|
|
38
40
|
status: "success";
|
|
39
41
|
data: TData;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-data",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.0.0",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@babel/runtime": "^7.18.6",
|
|
17
|
-
"@khanacademy/wonder-blocks-core": "^
|
|
17
|
+
"@khanacademy/wonder-blocks-core": "^6.0.1"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@khanacademy/wonder-stuff-core": "^1.2.2",
|
|
21
21
|
"react": "16.14.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"wb-dev-build-settings": "^0.
|
|
24
|
+
"@khanacademy/wb-dev-build-settings": "^1.0.0"
|
|
25
25
|
},
|
|
26
26
|
"author": "",
|
|
27
27
|
"license": "MIT"
|
|
@@ -47,7 +47,7 @@ describe("Data", () => {
|
|
|
47
47
|
|
|
48
48
|
it("should make request for data on construction", async () => {
|
|
49
49
|
// Arrange
|
|
50
|
-
const response = Promise.resolve("data");
|
|
50
|
+
const response: any = Promise.resolve("data");
|
|
51
51
|
const fakeHandler = jest.fn().mockReturnValue(response);
|
|
52
52
|
const fakeChildrenFn = jest.fn(() => null);
|
|
53
53
|
|
|
@@ -57,7 +57,6 @@ describe("Data", () => {
|
|
|
57
57
|
{fakeChildrenFn}
|
|
58
58
|
</Data>,
|
|
59
59
|
);
|
|
60
|
-
// @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call.
|
|
61
60
|
await act(() => response);
|
|
62
61
|
|
|
63
62
|
// Assert
|
|
@@ -66,7 +65,7 @@ describe("Data", () => {
|
|
|
66
65
|
|
|
67
66
|
it("should initially render children with loading", async () => {
|
|
68
67
|
// Arrange
|
|
69
|
-
const response = Promise.resolve("data");
|
|
68
|
+
const response: any = Promise.resolve("data");
|
|
70
69
|
const fakeHandler = jest.fn().mockReturnValue(response);
|
|
71
70
|
const fakeChildrenFn = jest.fn(() => null);
|
|
72
71
|
|
|
@@ -76,7 +75,6 @@ describe("Data", () => {
|
|
|
76
75
|
{fakeChildrenFn}
|
|
77
76
|
</Data>,
|
|
78
77
|
);
|
|
79
|
-
// @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call.
|
|
80
78
|
await act(() => response);
|
|
81
79
|
|
|
82
80
|
// Assert
|
|
@@ -272,7 +270,7 @@ describe("Data", () => {
|
|
|
272
270
|
|
|
273
271
|
it("should ignore resolution of pending handler fulfillment when id changes", async () => {
|
|
274
272
|
// Arrange
|
|
275
|
-
const oldRequest = Promise.resolve("OLD DATA");
|
|
273
|
+
const oldRequest: any = Promise.resolve("OLD DATA");
|
|
276
274
|
const oldHandler = jest
|
|
277
275
|
.fn()
|
|
278
276
|
.mockReturnValueOnce(oldRequest)
|
|
@@ -294,7 +292,6 @@ describe("Data", () => {
|
|
|
294
292
|
{fakeChildrenFn}
|
|
295
293
|
</Data>,
|
|
296
294
|
);
|
|
297
|
-
// @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call.
|
|
298
295
|
await act(() => oldRequest);
|
|
299
296
|
|
|
300
297
|
// Assert
|
|
@@ -343,14 +340,13 @@ describe("Data", () => {
|
|
|
343
340
|
|
|
344
341
|
it("should ignore catastrophic request fulfillment when id changes", async () => {
|
|
345
342
|
// Arrange
|
|
346
|
-
const catastrophe = Promise.resolve({
|
|
343
|
+
const catastrophe: any = Promise.resolve({
|
|
347
344
|
status: "error",
|
|
348
345
|
error: new Error("CATASTROPHE!"),
|
|
349
346
|
});
|
|
350
347
|
jest.spyOn(
|
|
351
348
|
RequestFulfillment.Default,
|
|
352
349
|
"fulfill",
|
|
353
|
-
// @ts-expect-error [FEI-5019] - TS2345 - Argument of type 'Promise<{ status: string; error: Error; }>' is not assignable to parameter of type 'Promise<Result<ValidCacheData>>'.
|
|
354
350
|
).mockReturnValueOnce(catastrophe);
|
|
355
351
|
const oldHandler = jest.fn().mockResolvedValue("OLD DATA");
|
|
356
352
|
|
|
@@ -367,7 +363,6 @@ describe("Data", () => {
|
|
|
367
363
|
</Data>,
|
|
368
364
|
);
|
|
369
365
|
await act(() =>
|
|
370
|
-
// @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call.
|
|
371
366
|
catastrophe.catch(() => {
|
|
372
367
|
/* ignore */
|
|
373
368
|
}),
|
|
@@ -428,8 +423,8 @@ describe("Data", () => {
|
|
|
428
423
|
|
|
429
424
|
it("should retain old data while reloading if retainResultOnChange is true", async () => {
|
|
430
425
|
// Arrange
|
|
431
|
-
const response1 = Promise.resolve("data1");
|
|
432
|
-
const response2 = Promise.resolve("data2");
|
|
426
|
+
const response1: any = Promise.resolve("data1");
|
|
427
|
+
const response2: any = Promise.resolve("data2");
|
|
433
428
|
const fakeHandler1 = () => response1;
|
|
434
429
|
const fakeHandler2 = () => response2;
|
|
435
430
|
const fakeChildrenFn = jest.fn(() => null);
|
|
@@ -445,7 +440,6 @@ describe("Data", () => {
|
|
|
445
440
|
</Data>,
|
|
446
441
|
);
|
|
447
442
|
fakeChildrenFn.mockClear();
|
|
448
|
-
// @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call.
|
|
449
443
|
await act(() => response1);
|
|
450
444
|
wrapper.rerender(
|
|
451
445
|
<Data
|
|
@@ -456,7 +450,6 @@ describe("Data", () => {
|
|
|
456
450
|
{fakeChildrenFn}
|
|
457
451
|
</Data>,
|
|
458
452
|
);
|
|
459
|
-
// @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call.
|
|
460
453
|
await act(() => response2);
|
|
461
454
|
|
|
462
455
|
// Assert
|
package/src/components/data.ts
CHANGED
|
@@ -48,7 +48,7 @@ type Props<
|
|
|
48
48
|
* loading state and data or error that gets retrieved from cache or loaded
|
|
49
49
|
* via the request if no cached value is available.
|
|
50
50
|
*/
|
|
51
|
-
children: (result: Result<TData>) => React.
|
|
51
|
+
children: (result: Result<TData>) => React.ReactElement | null;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
/**
|
|
@@ -62,13 +62,11 @@ const Data = <TData extends ValidCacheData>({
|
|
|
62
62
|
children,
|
|
63
63
|
retainResultOnChange = false,
|
|
64
64
|
clientBehavior = WhenClientSide.ExecuteWhenNoSuccessResult,
|
|
65
|
-
}: Props<TData>): React.ReactElement => {
|
|
65
|
+
}: Props<TData>): React.ReactElement | null => {
|
|
66
66
|
const result = useHydratableEffect(requestId, handler, {
|
|
67
67
|
retainResultOnChange,
|
|
68
68
|
clientBehavior,
|
|
69
69
|
});
|
|
70
|
-
// @ts-expect-error: React TS types don't allow functional components to return
|
|
71
|
-
// ReactNodes even though React itself does.
|
|
72
70
|
return children(result);
|
|
73
71
|
};
|
|
74
72
|
|