@khanacademy/wonder-blocks-data 3.2.0 → 4.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/es/index.js +356 -332
  3. package/dist/index.js +507 -456
  4. package/docs.md +17 -35
  5. package/package.json +1 -1
  6. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +7 -46
  7. package/src/__tests__/generated-snapshot.test.js +56 -122
  8. package/src/components/__tests__/data.test.js +372 -297
  9. package/src/components/__tests__/intercept-data.test.js +6 -30
  10. package/src/components/data.js +153 -21
  11. package/src/components/data.md +38 -69
  12. package/src/components/intercept-context.js +6 -2
  13. package/src/components/intercept-data.js +40 -51
  14. package/src/components/intercept-data.md +13 -27
  15. package/src/components/track-data.md +9 -23
  16. package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +17 -0
  17. package/src/hooks/__tests__/use-server-effect.test.js +217 -0
  18. package/src/hooks/__tests__/use-shared-cache.test.js +307 -0
  19. package/src/hooks/use-server-effect.js +45 -0
  20. package/src/hooks/use-shared-cache.js +106 -0
  21. package/src/index.js +15 -19
  22. package/src/util/__tests__/__snapshots__/scoped-in-memory-cache.test.js.snap +19 -0
  23. package/src/util/__tests__/request-fulfillment.test.js +42 -85
  24. package/src/util/__tests__/request-tracking.test.js +72 -191
  25. package/src/util/__tests__/{result-from-cache-entry.test.js → result-from-cache-response.test.js} +9 -10
  26. package/src/util/__tests__/scoped-in-memory-cache.test.js +396 -0
  27. package/src/util/__tests__/ssr-cache.test.js +639 -0
  28. package/src/util/request-fulfillment.js +36 -44
  29. package/src/util/request-tracking.js +62 -75
  30. package/src/util/{result-from-cache-entry.js → result-from-cache-response.js} +10 -13
  31. package/src/util/scoped-in-memory-cache.js +149 -0
  32. package/src/util/ssr-cache.js +206 -0
  33. package/src/util/types.js +43 -108
  34. package/src/hooks/__tests__/use-data.test.js +0 -826
  35. package/src/hooks/use-data.js +0 -143
  36. package/src/util/__tests__/memory-cache.test.js +0 -446
  37. package/src/util/__tests__/request-handler.test.js +0 -121
  38. package/src/util/__tests__/response-cache.test.js +0 -879
  39. package/src/util/memory-cache.js +0 -187
  40. package/src/util/request-handler.js +0 -42
  41. package/src/util/request-handler.md +0 -51
  42. package/src/util/response-cache.js +0 -213
@@ -5,8 +5,6 @@ import {render} from "@testing-library/react";
5
5
  import InterceptContext from "../intercept-context.js";
6
6
  import InterceptData from "../intercept-data.js";
7
7
 
8
- import type {IRequestHandler} from "../../util/types.js";
9
-
10
8
  describe("InterceptData", () => {
11
9
  afterEach(() => {
12
10
  jest.resetAllMocks();
@@ -14,15 +12,10 @@ describe("InterceptData", () => {
14
12
 
15
13
  it("should update context with fulfillRequest method", () => {
16
14
  // Arrange
17
- const fakeHandler: IRequestHandler<string, string> = {
18
- fulfillRequest: () => Promise.resolve("data"),
19
- getKey: (o) => o,
20
- type: "MY_HANDLER",
21
- hydrate: true,
22
- };
15
+ const fakeHandler = () => Promise.resolve("data");
23
16
  const props = {
24
17
  handler: fakeHandler,
25
- fulfillRequest: jest.fn(),
18
+ requestId: "ID",
26
19
  };
27
20
  const captureContextFn = jest.fn();
28
21
 
@@ -38,36 +31,21 @@ describe("InterceptData", () => {
38
31
  // Assert
39
32
  expect(captureContextFn).toHaveBeenCalledWith(
40
33
  expect.objectContaining({
41
- MY_HANDLER: {
42
- fulfillRequest: props.fulfillRequest,
43
- },
34
+ ID: props.handler,
44
35
  }),
45
36
  );
46
37
  });
47
38
 
48
39
  it("should override parent InterceptData", () => {
49
40
  // Arrange
50
- const fakeHandler: IRequestHandler<string, string> = {
51
- fulfillRequest: () => Promise.resolve("data"),
52
- getKey: (o) => o,
53
- type: "MY_HANDLER",
54
- cache: null,
55
- hydrate: true,
56
- };
57
41
  const fulfillRequest1Fn = jest.fn();
58
42
  const fulfillRequest2Fn = jest.fn();
59
43
  const captureContextFn = jest.fn();
60
44
 
61
45
  // Act
62
46
  render(
63
- <InterceptData
64
- handler={fakeHandler}
65
- fulfillRequest={fulfillRequest1Fn}
66
- >
67
- <InterceptData
68
- handler={fakeHandler}
69
- fulfillRequest={fulfillRequest2Fn}
70
- >
47
+ <InterceptData handler={fulfillRequest1Fn} requestId="ID">
48
+ <InterceptData handler={fulfillRequest2Fn} requestId="ID">
71
49
  <InterceptContext.Consumer>
72
50
  {captureContextFn}
73
51
  </InterceptContext.Consumer>
@@ -78,9 +56,7 @@ describe("InterceptData", () => {
78
56
  // Assert
79
57
  expect(captureContextFn).toHaveBeenCalledWith(
80
58
  expect.objectContaining({
81
- MY_HANDLER: {
82
- fulfillRequest: fulfillRequest2Fn,
83
- },
59
+ ID: fulfillRequest2Fn,
84
60
  }),
85
61
  );
86
62
  });
@@ -1,36 +1,63 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
 
4
- import {useData} from "../hooks/use-data.js";
4
+ import {Server} from "@khanacademy/wonder-blocks-core";
5
+ import {RequestFulfillment} from "../util/request-fulfillment.js";
6
+ import InterceptContext from "./intercept-context.js";
7
+ import {useServerEffect} from "../hooks/use-server-effect.js";
8
+ import {resultFromCachedResponse} from "../util/result-from-cache-response.js";
5
9
 
6
- import type {Result, IRequestHandler, ValidData} from "../util/types.js";
10
+ import type {Result, ValidCacheData} from "../util/types.js";
7
11
 
8
12
  type Props<
9
- /**
10
- * The type of options that the handler requires to define a request.
11
- */
12
- TOptions,
13
13
  /**
14
14
  * The type of data resolved by the handler's fulfillRequest method.
15
15
  */
16
- TData,
16
+ TData: ValidCacheData,
17
17
  > = {|
18
18
  /**
19
- * An `IRequestHandler` instance of the type this component will use to
20
- * resolve its requests.
19
+ * A unique identifier for the request.
20
+ *
21
+ * This should not be shared by other uses of this component.
22
+ */
23
+ requestId: string,
24
+
25
+ /**
26
+ * This defines how the request is fulfilled.
21
27
  *
22
- * The framework deduplicates handlers based on their `type` property.
23
- * Handlers with the same `type` property are assumed to be the same.
28
+ * If this is changed without changing the ID, there are cases where the
29
+ * old handler result may be given. This is not a supported mode of
30
+ * operation.
24
31
  */
25
- handler: IRequestHandler<TOptions, TData>,
32
+ handler: () => Promise<?TData>,
26
33
 
27
34
  /**
28
- * The handler-specific options that define what requestt is to be made.
35
+ * When true, the result will be hydrated when client-side. Otherwise,
36
+ * the request will be fulfilled for us in SSR but will be ignored during
37
+ * hydration. Only set this to false if you know some other mechanism
38
+ * will be performing hydration (such as if requests are fulfilled by
39
+ * Apollo Client but you consolidated all SSR requests using WB Data).
29
40
  *
30
- * Changing these options will only cause the data to update if the key
31
- * from `handler.getKey(options)` changes.
41
+ * Defaults to true.
42
+ */
43
+ hydrate?: boolean,
44
+
45
+ /**
46
+ * When true, the children will be rendered with the existing result
47
+ * until the pending load is completed. Otherwise, the children will be
48
+ * given a loading state until the request is fulfilled.
49
+ *
50
+ * Defaults to false.
51
+ */
52
+ showOldDataWhileLoading?: boolean,
53
+
54
+ /**
55
+ * When true, the handler will always be invoked after hydration.
56
+ * This defaults to false.
57
+ * NOTE: The request is invoked after hydration if the hydrated result
58
+ * is an error.
32
59
  */
33
- options: TOptions,
60
+ alwaysRequestOnHydration?: boolean,
34
61
 
35
62
  /**
36
63
  * A function that will render the content of this component using the
@@ -45,10 +72,115 @@ type Props<
45
72
  * requirements can be placed in a React application in a manner that will
46
73
  * support server-side rendering and efficient caching.
47
74
  */
48
- const Data = <TOptions, TData: ValidData>(
49
- props: Props<TOptions, TData>,
50
- ): React.Node => {
51
- const data = useData(props.handler, props.options);
52
- return props.children(data);
75
+ const Data = <TData: ValidCacheData>({
76
+ requestId,
77
+ handler,
78
+ children,
79
+ hydrate,
80
+ showOldDataWhileLoading,
81
+ alwaysRequestOnHydration,
82
+ }: Props<TData>): React.Node => {
83
+ // Lookup to see if there's an interceptor for the handler.
84
+ // If we have one, we need to replace the handler with one that
85
+ // uses the interceptor.
86
+ const interceptorMap = React.useContext(InterceptContext);
87
+
88
+ // If we have an interceptor, we need to replace the handler with one
89
+ // that uses the interceptor. This helper function generates a new
90
+ // handler.
91
+ const maybeInterceptedHandler = React.useMemo(() => {
92
+ const interceptor = interceptorMap[requestId];
93
+ if (interceptor == null) {
94
+ return handler;
95
+ }
96
+ return () => interceptor() ?? handler();
97
+ }, [handler, interceptorMap, requestId]);
98
+
99
+ const hydrateResult = useServerEffect(
100
+ requestId,
101
+ maybeInterceptedHandler,
102
+ hydrate,
103
+ );
104
+ const [currentResult, setResult] = React.useState(hydrateResult);
105
+
106
+ // Here we make sure the request still occurs client-side as needed.
107
+ // This is for legacy usage that expects this. Eventually we will want
108
+ // to deprecate.
109
+ React.useEffect(() => {
110
+ // This is here until I can do a better documentation example for
111
+ // the TrackData docs.
112
+ // istanbul ignore next
113
+ if (Server.isServerSide()) {
114
+ return;
115
+ }
116
+
117
+ // We don't bother with this if we have hydration data and we're not
118
+ // forcing a request on hydration.
119
+ // We don't care if these things change after the first render,
120
+ // so we don't want them in the inputs array.
121
+ if (!alwaysRequestOnHydration && hydrateResult?.data != null) {
122
+ return;
123
+ }
124
+
125
+ // If we're not hydrating a result and we're not going to render
126
+ // with old data until we're loaded, we want to make sure we set our
127
+ // result to null so that we're in the loading state.
128
+ if (!showOldDataWhileLoading) {
129
+ // Mark ourselves as loading.
130
+ setResult(null);
131
+ }
132
+
133
+ // We aren't server-side, so let's make the request.
134
+ // We don't need to use our built-in request fulfillment here if we
135
+ // don't want, but it does mean we'll share inflight requests for the
136
+ // same ID and the result will be in the same format as the
137
+ // hydrated value.
138
+ let cancel = false;
139
+ RequestFulfillment.Default.fulfill(requestId, {
140
+ handler: maybeInterceptedHandler,
141
+ })
142
+ .then((result) => {
143
+ if (cancel) {
144
+ return;
145
+ }
146
+ setResult(result);
147
+ return;
148
+ })
149
+ .catch((e) => {
150
+ if (cancel) {
151
+ return;
152
+ }
153
+ /**
154
+ * We should never get here as errors in fulfillment are part
155
+ * of the `then`, but if we do.
156
+ */
157
+ // eslint-disable-next-line no-console
158
+ console.error(
159
+ `Unexpected error occurred during data fulfillment: ${e}`,
160
+ );
161
+ setResult({
162
+ error: typeof e === "string" ? e : e.message,
163
+ });
164
+ return;
165
+ });
166
+
167
+ return () => {
168
+ cancel = true;
169
+ };
170
+ // If the handler changes, we don't care. The ID is what indicates
171
+ // the request that should be made and folks shouldn't be changing the
172
+ // handler without changing the ID as well.
173
+ // In addition, we don't want to include hydrateResult nor
174
+ // alwaysRequestOnHydration as them changinng after the first pass
175
+ // is irrelevant.
176
+ // Finally, we don't want to include showOldDataWhileLoading as that
177
+ // changing on its own is also not relevant. It only matters if the
178
+ // request itself changes. All of which is to say that we only
179
+ // run this effect for the ID changing.
180
+ // eslint-disable-next-line react-hooks/exhaustive-deps
181
+ }, [requestId]);
182
+
183
+ return children(resultFromCachedResponse(currentResult));
53
184
  };
185
+
54
186
  export default Data;
@@ -1,7 +1,7 @@
1
- The `Data` component is the frontend piece of our data architecture that
2
- most folks will use. It describes a data requirement in terms of a handler, and
3
- some options. Handlers must implement the
4
- `IRequestHandler` interface.
1
+ The `Data` component is the frontend piece of our data architecture.
2
+ It describes a data requirement in terms of a handler and an identifier.
3
+ It also has props to govern hydrate behavior as well as loading and client-side
4
+ request behavior.
5
5
 
6
6
  The handler is responsible for fulfilling the request when asked to do so.
7
7
 
@@ -40,49 +40,30 @@ data or an error, we re-render.
40
40
  ```jsx
41
41
  import {Body, BodyMonospace} from "@khanacademy/wonder-blocks-typography";
42
42
  import {View} from "@khanacademy/wonder-blocks-core";
43
- import {Data, RequestHandler} from "@khanacademy/wonder-blocks-data";
43
+ import {Data} from "@khanacademy/wonder-blocks-data";
44
44
  import {Strut} from "@khanacademy/wonder-blocks-layout";
45
45
  import Color from "@khanacademy/wonder-blocks-color";
46
46
  import Spacing from "@khanacademy/wonder-blocks-spacing";
47
47
 
48
- class MyValidHandler extends RequestHandler {
49
- constructor() {
50
- super("CACHE_MISS_HANDLER_VALID");
51
- }
52
-
53
- fulfillRequest(options) {
54
- return new Promise((resolve, reject) =>
55
- setTimeout(() => resolve("I'm DATA from a request"), 3000),
56
- );
57
- }
58
- }
59
-
60
- class MyInvalidHandler extends RequestHandler {
61
- constructor() {
62
- super("CACHE_MISS_HANDLER_ERROR");
63
- }
48
+ const myValidHandler = () => new Promise((resolve, reject) =>
49
+ setTimeout(() => resolve("I'm DATA from a request"), 3000),
50
+ );
64
51
 
65
- fulfillRequest(options) {
66
- return new Promise((resolve, reject) =>
67
- setTimeout(() => reject("I'm an ERROR from a request"), 3000),
68
- );
69
- }
70
- }
71
-
72
- const valid = new MyValidHandler();
73
- const invalid = new MyInvalidHandler();
52
+ const myInvalidHandler = () => new Promise((resolve, reject) =>
53
+ setTimeout(() => reject("I'm an ERROR from a request"), 3000),
54
+ );
74
55
 
75
56
  <View>
76
57
  <View>
77
58
  <Body>This request will succeed and give us data!</Body>
78
- <Data handler={valid} options={{some: "options"}}>
79
- {({loading, data}) => {
80
- if (loading) {
59
+ <Data handler={myValidHandler} requestId="VALID">
60
+ {(result) => {
61
+ if (result.status === "loading") {
81
62
  return "Loading...";
82
63
  }
83
64
 
84
65
  return (
85
- <BodyMonospace>{data}</BodyMonospace>
66
+ <BodyMonospace>{result.data}</BodyMonospace>
86
67
  );
87
68
  }}
88
69
  </Data>
@@ -90,14 +71,14 @@ const invalid = new MyInvalidHandler();
90
71
  <Strut size={Spacing.small_12} />
91
72
  <View>
92
73
  <Body>This request will go boom and give us an error!</Body>
93
- <Data handler={invalid} options={{some: "options"}}>
94
- {({loading, error}) => {
95
- if (loading) {
74
+ <Data handler={myInvalidHandler} requestId="INVALID">
75
+ {(result) => {
76
+ if (result.status === "loading") {
96
77
  return "Loading...";
97
78
  }
98
79
 
99
80
  return (
100
- <BodyMonospace style={{color: Color.red}}>ERROR: {error}</BodyMonospace>
81
+ <BodyMonospace style={{color: Color.red}}>ERROR: {result.error}</BodyMonospace>
101
82
  );
102
83
  }}
103
84
  </Data>
@@ -114,49 +95,37 @@ populated using the `initializeCache` method before rendering.
114
95
  ```jsx
115
96
  import {Body, BodyMonospace} from "@khanacademy/wonder-blocks-typography";
116
97
  import {View} from "@khanacademy/wonder-blocks-core";
117
- import {Data, RequestHandler, initializeCache} from "@khanacademy/wonder-blocks-data";
98
+ import {Data, initializeCache} from "@khanacademy/wonder-blocks-data";
118
99
  import {Strut} from "@khanacademy/wonder-blocks-layout";
119
100
  import Color from "@khanacademy/wonder-blocks-color";
120
101
  import Spacing from "@khanacademy/wonder-blocks-spacing";
121
102
 
122
- class MyHandler extends RequestHandler {
123
- constructor() {
124
- super("CACHE_HIT_HANDLER");
125
- }
126
-
127
- /**
128
- * fulfillRequest should not get called as we already have data cached.
129
- */
130
- fulfillRequest(options) {
131
- throw new Error(
132
- "If you're seeing this error, the examples are broken and data isn't in the cache that should be.",
133
- );
134
- }
135
- }
103
+ const myHandler = () => {
104
+ throw new Error(
105
+ "If you're seeing this error, the examples are broken and data isn't in the cache that should be.",
106
+ );
107
+ };
136
108
 
137
- const handler = new MyHandler();
138
109
  initializeCache({
139
- CACHE_HIT_HANDLER: {
140
- DATA: {
141
- data: "I'm DATA from the hydration cache"
142
- },
143
- ERROR: {
144
- error: "I'm an ERROR from hydration cache"
145
- }
110
+ DATA: {
111
+ data: "I'm DATA from the hydration cache"
112
+ },
113
+ ERROR: {
114
+ error: "I'm an ERROR from hydration cache"
146
115
  }
147
116
  });
148
117
 
149
118
  <View>
150
119
  <View>
151
120
  <Body>This cache has data!</Body>
152
- <Data handler={handler} options={"DATA"}>
153
- {({loading, data}) => {
154
- if (loading) {
121
+ <Data handler={myHandler} requestId="DATA">
122
+ {(result) => {
123
+ if (result.status !== "success") {
155
124
  return "If you see this, the example is broken!";
156
125
  }
157
126
 
158
127
  return (
159
- <BodyMonospace>{data}</BodyMonospace>
128
+ <BodyMonospace>{result.data}</BodyMonospace>
160
129
  );
161
130
  }}
162
131
  </Data>
@@ -164,14 +133,14 @@ initializeCache({
164
133
  <Strut size={Spacing.small_12} />
165
134
  <View>
166
135
  <Body>This cache has error!</Body>
167
- <Data handler={handler} options={"ERROR"}>
168
- {({loading, error}) => {
169
- if (loading) {
136
+ <Data handler={myHandler} requestId="ERROR">
137
+ {(result) => {
138
+ if (result.status !== "error") {
170
139
  return "If you see this, the example is broken!";
171
140
  }
172
141
 
173
142
  return (
174
- <BodyMonospace style={{color: Color.red}}>ERROR: {error}</BodyMonospace>
143
+ <BodyMonospace style={{color: Color.red}}>ERROR: {result.error}</BodyMonospace>
175
144
  );
176
145
  }}
177
146
  </Data>
@@ -1,10 +1,14 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
+ import type {ValidCacheData} from "../util/types.js";
3
4
 
4
- import type {InterceptContextData} from "../util/types.js";
5
+ type InterceptContextData = {
6
+ [id: string]: <TData: ValidCacheData>() => ?Promise<?TData>,
7
+ ...
8
+ };
5
9
 
6
10
  /**
7
- * InterceptContext defines a map from handler type to interception methods.
11
+ * InterceptContext defines a map from request ID to interception methods.
8
12
  *
9
13
  * INTERNAL USE ONLY
10
14
  */
@@ -3,75 +3,64 @@ import * as React from "react";
3
3
 
4
4
  import InterceptContext from "./intercept-context.js";
5
5
 
6
- import type {
7
- ValidData,
8
- IRequestHandler,
9
- InterceptFulfillRequestFn,
10
- } from "../util/types.js";
6
+ import type {ValidCacheData} from "../util/types.js";
11
7
 
12
- type Props<TOptions, TData> = {|
8
+ type Props<TData: ValidCacheData> = {|
13
9
  /**
14
- * A handler of the type to be intercepted.
10
+ * The ID of the request to intercept.
15
11
  */
16
- handler: IRequestHandler<TOptions, TData>,
12
+ requestId: string,
17
13
 
18
14
  /**
19
- * The children to render within this component. Any requests by `Data`
20
- * components that use a handler of the same type as the handler for this
21
- * component that are rendered within these children will be intercepted by
22
- * this component (unless another `InterceptData` component overrides this
23
- * one).
15
+ * Called to intercept and fulfill the request.
16
+ * If this returns null, the request will be fulfilled by the
17
+ * handler of the original request being intercepted.
24
18
  */
25
- children: React.Node,
19
+ handler: () => ?Promise<?TData>,
26
20
 
27
21
  /**
28
- * Called to fulfill a request.
29
- * If this returns null, the request will be fulfilled by the
30
- * handler of the original request being intercepted.
22
+ * The children to render within this component. Any requests by `Data`
23
+ * components that use same ID as this component will be intercepted.
24
+ * (unless another `InterceptData` component overrides this one).
31
25
  */
32
- fulfillRequest: InterceptFulfillRequestFn<TOptions, TData>,
26
+ children: React.Node,
33
27
  |};
34
28
 
35
29
  /**
36
- * This component provides a mechanism to intercept the data requests for the
37
- * type of a given handler and provide alternative results. This is mostly
38
- * useful for testing.
30
+ * This component provides a mechanism to intercept data requests.
31
+ * This is for use in testing.
39
32
  *
40
33
  * This component is not recommended for use in production code as it
41
34
  * can prevent predictable functioning of the Wonder Blocks Data framework.
42
35
  * One possible side-effect is that inflight requests from the interceptor could
43
- * be picked up by `Data` component requests of the same handler type from
44
- * outside the children of this component.
36
+ * be picked up by `Data` component requests from outside the children of this
37
+ * component.
45
38
  *
46
39
  * These components do not chain. If a different `InterceptData` instance is
47
- * rendered within this one that intercepts the same handler type, then that
40
+ * rendered within this one that intercepts the same id, then that
48
41
  * new instance will replace this interceptor for its children. All methods
49
42
  * will be replaced.
50
43
  */
51
- export default class InterceptData<
52
- TOptions,
53
- TData: ValidData,
54
- > extends React.Component<Props<TOptions, TData>> {
55
- render(): React.Node {
56
- return (
57
- <InterceptContext.Consumer>
58
- {(value) => {
59
- const handlerType = this.props.handler.type;
60
- const interceptor = {
61
- ...value[handlerType],
62
- fulfillRequest: this.props.fulfillRequest,
63
- };
64
- const newValue = {
65
- ...value,
66
- [handlerType]: interceptor,
67
- };
68
- return (
69
- <InterceptContext.Provider value={newValue}>
70
- {this.props.children}
71
- </InterceptContext.Provider>
72
- );
73
- }}
74
- </InterceptContext.Consumer>
75
- );
76
- }
77
- }
44
+ const InterceptData = <TData: ValidCacheData>({
45
+ requestId,
46
+ handler,
47
+ children,
48
+ }: Props<TData>): React.Node => {
49
+ const interceptMap = React.useContext(InterceptContext);
50
+
51
+ const updatedInterceptMap = React.useMemo(
52
+ () => ({
53
+ ...interceptMap,
54
+ [requestId]: handler,
55
+ }),
56
+ [interceptMap, requestId, handler],
57
+ );
58
+
59
+ return (
60
+ <InterceptContext.Provider value={updatedInterceptMap}>
61
+ {children}
62
+ </InterceptContext.Provider>
63
+ );
64
+ };
65
+
66
+ export default InterceptData;
@@ -2,18 +2,18 @@ When you want to generate tests that check the loading state and
2
2
  subsequent loaded state are working correctly for your uses of `Data` you can
3
3
  use the `InterceptData` component.
4
4
 
5
- This component takes four props; children to be rendered, the handler of the
6
- type of data requests that are to be intercepted, and a `fulfillRequest`.
5
+ This component takes three props; children to be rendered, the handler of the
6
+ to fulfill the request, and the id of the request that is being intercepted.
7
7
 
8
8
  Note that this component is expected to be used only within test cases and
9
9
  usually only as a single instance. In flight requests for a given handler
10
10
  type can be shared and as such, using `InterceptData` alongside non-intercepted
11
- `Data` components with the same handler type can have indeterminate outcomes.
11
+ `Data` components with the same id can have indeterminate outcomes.
12
12
 
13
- The `fulfillRequest` intercept function has the form:
13
+ The `handler` intercept function has the form:
14
14
 
15
15
  ```js static
16
- (options: TOptions) => ?Promise<TData>;
16
+ () => ?Promise<?TData>;
17
17
  ```
18
18
 
19
19
  If this method returns `null`, the default behavior occurs. This
@@ -23,40 +23,26 @@ means that a request will be made for data via the handler assigned to the
23
23
  ```jsx
24
24
  import {Body, BodyMonospace} from "@khanacademy/wonder-blocks-typography";
25
25
  import {View} from "@khanacademy/wonder-blocks-core";
26
- import {InterceptData, Data, RequestHandler} from "@khanacademy/wonder-blocks-data";
26
+ import {InterceptData, Data} from "@khanacademy/wonder-blocks-data";
27
27
  import {Strut} from "@khanacademy/wonder-blocks-layout";
28
28
  import Color from "@khanacademy/wonder-blocks-color";
29
29
  import Spacing from "@khanacademy/wonder-blocks-spacing";
30
30
 
31
- class MyHandler extends RequestHandler {
32
- constructor() {
33
- super("INTERCEPT_DATA_HANDLER1");
34
- }
31
+ const myHandler = () => Promise.reject(new Error("You should not see this!"));
35
32
 
36
- fulfillRequest(options) {
37
- return Promise.reject(new Error("You should not see this!"));
38
- }
39
- }
33
+ const interceptHandler = () => Promise.resolve("INTERCEPTED DATA!");
40
34
 
41
- const handler = new MyHandler();
42
- const fulfillRequestInterceptor = function(options) {
43
- if (options === "DATA") {
44
- return Promise.resolve("INTERCEPTED DATA!");
45
- }
46
- return null;
47
- };
48
-
49
- <InterceptData handler={handler} fulfillRequest={fulfillRequestInterceptor}>
35
+ <InterceptData handler={interceptHandler} requestId="INTERCEPT_EXAMPLE">
50
36
  <View>
51
37
  <Body>This received intercepted data!</Body>
52
- <Data handler={handler} options={"DATA"}>
53
- {({loading, data}) => {
54
- if (loading) {
38
+ <Data handler={myHandler} requestId="INTERCEPT_EXAMPLE">
39
+ {(result) => {
40
+ if (result.status !== "success") {
55
41
  return "If you see this, the example is broken!";
56
42
  }
57
43
 
58
44
  return (
59
- <BodyMonospace>{data}</BodyMonospace>
45
+ <BodyMonospace>{result.data}</BodyMonospace>
60
46
  );
61
47
  }}
62
48
  </Data>