@khanacademy/wonder-blocks-data 2.3.4 → 3.1.1

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 (48) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/es/index.js +368 -429
  3. package/dist/index.js +457 -460
  4. package/docs.md +19 -13
  5. package/package.json +3 -3
  6. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +40 -160
  7. package/src/__tests__/generated-snapshot.test.js +15 -195
  8. package/src/components/__tests__/data.test.js +159 -965
  9. package/src/components/__tests__/gql-router.test.js +64 -0
  10. package/src/components/__tests__/intercept-data.test.js +9 -66
  11. package/src/components/__tests__/track-data.test.js +6 -5
  12. package/src/components/data.js +9 -117
  13. package/src/components/data.md +38 -60
  14. package/src/components/gql-router.js +66 -0
  15. package/src/components/intercept-data.js +2 -34
  16. package/src/components/intercept-data.md +7 -105
  17. package/src/hooks/__tests__/use-data.test.js +826 -0
  18. package/src/hooks/__tests__/use-gql.test.js +233 -0
  19. package/src/hooks/use-data.js +143 -0
  20. package/src/hooks/use-gql.js +77 -0
  21. package/src/index.js +13 -9
  22. package/src/util/__tests__/get-gql-data-from-response.test.js +187 -0
  23. package/src/util/__tests__/memory-cache.test.js +134 -35
  24. package/src/util/__tests__/request-fulfillment.test.js +21 -36
  25. package/src/util/__tests__/request-handler.test.js +30 -30
  26. package/src/util/__tests__/request-tracking.test.js +29 -30
  27. package/src/util/__tests__/response-cache.test.js +521 -561
  28. package/src/util/__tests__/result-from-cache-entry.test.js +68 -0
  29. package/src/util/get-gql-data-from-response.js +69 -0
  30. package/src/util/gql-error.js +36 -0
  31. package/src/util/gql-router-context.js +6 -0
  32. package/src/util/gql-types.js +65 -0
  33. package/src/util/memory-cache.js +18 -14
  34. package/src/util/request-fulfillment.js +4 -0
  35. package/src/util/request-handler.js +2 -27
  36. package/src/util/request-handler.md +0 -32
  37. package/src/util/response-cache.js +50 -110
  38. package/src/util/result-from-cache-entry.js +38 -0
  39. package/src/util/types.js +14 -35
  40. package/LICENSE +0 -21
  41. package/src/components/__tests__/intercept-cache.test.js +0 -124
  42. package/src/components/__tests__/internal-data.test.js +0 -1030
  43. package/src/components/intercept-cache.js +0 -79
  44. package/src/components/intercept-cache.md +0 -103
  45. package/src/components/internal-data.js +0 -219
  46. package/src/util/__tests__/no-cache.test.js +0 -112
  47. package/src/util/no-cache.js +0 -67
  48. package/src/util/no-cache.md +0 -66
@@ -0,0 +1,64 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import {render} from "@testing-library/react";
4
+
5
+ import {GqlRouterContext} from "../../util/gql-router-context.js";
6
+ import {GqlRouter} from "../gql-router.js";
7
+
8
+ describe("GqlRouter", () => {
9
+ it("should provide the GqlRouterContext as configured", async () => {
10
+ // Arrange
11
+ const defaultContext = {
12
+ foo: "bar",
13
+ };
14
+ const fetch = jest.fn();
15
+ const CaptureContext = ({captureFn}) => {
16
+ captureFn(React.useContext(GqlRouterContext));
17
+ return null;
18
+ };
19
+
20
+ // Act
21
+ const result = await new Promise((resolve, reject) => {
22
+ render(
23
+ <GqlRouter defaultContext={defaultContext} fetch={fetch}>
24
+ <CaptureContext captureFn={resolve} />
25
+ </GqlRouter>,
26
+ );
27
+ });
28
+
29
+ // Assert
30
+ expect(result).toStrictEqual({
31
+ defaultContext,
32
+ fetch,
33
+ });
34
+ });
35
+
36
+ it("should not render React.memo-ized children if props remain the same", () => {
37
+ // Arrange
38
+ const defaultContext = {
39
+ foo: "bar",
40
+ };
41
+ const fetch = jest.fn();
42
+ let renderCount = 0;
43
+ const Child = React.memo(() => {
44
+ const context = React.useContext(GqlRouterContext);
45
+ renderCount++;
46
+ return <div>{JSON.stringify(context)}</div>;
47
+ });
48
+
49
+ // Act
50
+ const {rerender} = render(
51
+ <GqlRouter defaultContext={defaultContext} fetch={fetch}>
52
+ <Child />
53
+ </GqlRouter>,
54
+ );
55
+ rerender(
56
+ <GqlRouter defaultContext={defaultContext} fetch={fetch}>
57
+ <Child />
58
+ </GqlRouter>,
59
+ );
60
+
61
+ // Assert
62
+ expect(renderCount).toBe(1);
63
+ });
64
+ });
@@ -1,10 +1,9 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
- import {mount} from "enzyme";
3
+ import {render} from "@testing-library/react";
4
4
 
5
5
  import InterceptContext from "../intercept-context.js";
6
6
  import InterceptData from "../intercept-data.js";
7
- import InterceptCache from "../intercept-cache.js";
8
7
 
9
8
  import type {IRequestHandler} from "../../util/types.js";
10
9
 
@@ -13,31 +12,22 @@ describe("InterceptData", () => {
13
12
  jest.resetAllMocks();
14
13
  });
15
14
 
16
- it.each([
17
- ["with only fulfillRequest method", {fulfillRequest: jest.fn()}],
18
- [
19
- "with only shouldRefreshCache method",
20
- {shouldRefreshCache: jest.fn()},
21
- ],
22
- [
23
- "with both fulfillRequest and shouldRefreshCache methods",
24
- {fulfillRequest: jest.fn(), shouldRefreshCache: jest.fn()},
25
- ],
26
- ])("should update context %s", (_, props) => {
15
+ it("should update context with fulfillRequest method", () => {
27
16
  // Arrange
28
17
  const fakeHandler: IRequestHandler<string, string> = {
29
18
  fulfillRequest: () => Promise.resolve("data"),
30
19
  getKey: (o) => o,
31
- shouldRefreshCache: () => false,
32
20
  type: "MY_HANDLER",
33
- cache: null,
34
21
  hydrate: true,
35
22
  };
36
- props.handler = fakeHandler;
23
+ const props = {
24
+ handler: fakeHandler,
25
+ fulfillRequest: jest.fn(),
26
+ };
37
27
  const captureContextFn = jest.fn();
38
28
 
39
29
  // Act
40
- mount(
30
+ render(
41
31
  <InterceptData {...props}>
42
32
  <InterceptContext.Consumer>
43
33
  {captureContextFn}
@@ -49,8 +39,7 @@ describe("InterceptData", () => {
49
39
  expect(captureContextFn).toHaveBeenCalledWith(
50
40
  expect.objectContaining({
51
41
  MY_HANDLER: {
52
- fulfillRequest: props.fulfillRequest || null,
53
- shouldRefreshCache: props.shouldRefreshCache || null,
42
+ fulfillRequest: props.fulfillRequest,
54
43
  },
55
44
  }),
56
45
  );
@@ -61,28 +50,23 @@ describe("InterceptData", () => {
61
50
  const fakeHandler: IRequestHandler<string, string> = {
62
51
  fulfillRequest: () => Promise.resolve("data"),
63
52
  getKey: (o) => o,
64
- shouldRefreshCache: () => false,
65
53
  type: "MY_HANDLER",
66
54
  cache: null,
67
55
  hydrate: true,
68
56
  };
69
57
  const fulfillRequest1Fn = jest.fn();
70
- const shouldRefreshCache1Fn = jest.fn();
71
58
  const fulfillRequest2Fn = jest.fn();
72
- const shouldRefreshCache2Fn = jest.fn();
73
59
  const captureContextFn = jest.fn();
74
60
 
75
61
  // Act
76
- mount(
62
+ render(
77
63
  <InterceptData
78
64
  handler={fakeHandler}
79
65
  fulfillRequest={fulfillRequest1Fn}
80
- shouldRefreshCache={shouldRefreshCache1Fn}
81
66
  >
82
67
  <InterceptData
83
68
  handler={fakeHandler}
84
69
  fulfillRequest={fulfillRequest2Fn}
85
- shouldRefreshCache={shouldRefreshCache2Fn}
86
70
  >
87
71
  <InterceptContext.Consumer>
88
72
  {captureContextFn}
@@ -96,47 +80,6 @@ describe("InterceptData", () => {
96
80
  expect.objectContaining({
97
81
  MY_HANDLER: {
98
82
  fulfillRequest: fulfillRequest2Fn,
99
- shouldRefreshCache: shouldRefreshCache2Fn,
100
- },
101
- }),
102
- );
103
- });
104
-
105
- it("should not change InterceptCache methods on existing interceptor", () => {
106
- // Arrange
107
- const fakeHandler: IRequestHandler<string, string> = {
108
- fulfillRequest: () => Promise.resolve("data"),
109
- getKey: (o) => o,
110
- shouldRefreshCache: () => false,
111
- type: "MY_HANDLER",
112
- cache: null,
113
- hydrate: true,
114
- };
115
- const fulfillRequestFn = jest.fn();
116
- const getEntryFn = jest.fn();
117
- const captureContextFn = jest.fn();
118
-
119
- // Act
120
- mount(
121
- <InterceptCache handler={fakeHandler} getEntry={getEntryFn}>
122
- <InterceptData
123
- handler={fakeHandler}
124
- fulfillRequest={fulfillRequestFn}
125
- >
126
- <InterceptContext.Consumer>
127
- {captureContextFn}
128
- </InterceptContext.Consumer>
129
- </InterceptData>
130
- </InterceptCache>,
131
- );
132
-
133
- // Assert
134
- expect(captureContextFn).toHaveBeenCalledWith(
135
- expect.objectContaining({
136
- MY_HANDLER: {
137
- fulfillRequest: fulfillRequestFn,
138
- shouldRefreshCache: null,
139
- getEntry: getEntryFn,
140
83
  },
141
84
  }),
142
85
  );
@@ -1,7 +1,7 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import {Server} from "@khanacademy/wonder-blocks-core";
4
- import {mount, shallow} from "enzyme";
4
+ import {render, screen} from "@testing-library/react";
5
5
 
6
6
  import TrackData from "../track-data.js";
7
7
  import {RequestTracker, TrackerContext} from "../../util/request-tracking.js";
@@ -16,7 +16,7 @@ describe("TrackData", () => {
16
16
  jest.spyOn(Server, "isServerSide").mockReturnValue(false);
17
17
 
18
18
  // Act
19
- const underTest = () => shallow(<TrackData>SOME CHILDREN</TrackData>);
19
+ const underTest = () => render(<TrackData>SOME CHILDREN</TrackData>);
20
20
 
21
21
  // Assert
22
22
  expect(underTest).toThrowErrorMatchingInlineSnapshot(
@@ -29,10 +29,11 @@ describe("TrackData", () => {
29
29
  jest.spyOn(Server, "isServerSide").mockReturnValue(true);
30
30
 
31
31
  // Act
32
- const result = shallow(<TrackData>SOME CHILDREN</TrackData>);
32
+ render(<TrackData>SOME CHILDREN</TrackData>);
33
+ const result = screen.getByText("SOME CHILDREN");
33
34
 
34
35
  // Assert
35
- expect(result).toHaveHTML("SOME CHILDREN");
36
+ expect(result).toBeInTheDocument();
36
37
  });
37
38
 
38
39
  it("should provide tracker function for tracking context", async () => {
@@ -41,7 +42,7 @@ describe("TrackData", () => {
41
42
 
42
43
  // Act
43
44
  const result = await new Promise((resolve, reject) => {
44
- mount(
45
+ render(
45
46
  <TrackData>
46
47
  <TrackerContext.Consumer>
47
48
  {(fn) => resolve(fn)}
@@ -1,18 +1,9 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
 
4
- import {ResponseCache} from "../util/response-cache.js";
5
- import InternalData from "./internal-data.js";
4
+ import {useData} from "../hooks/use-data.js";
6
5
 
7
- import InterceptContext from "./intercept-context.js";
8
-
9
- import type {
10
- CacheEntry,
11
- Interceptor,
12
- Result,
13
- IRequestHandler,
14
- ValidData,
15
- } from "../util/types.js";
6
+ import type {Result, IRequestHandler, ValidData} from "../util/types.js";
16
7
 
17
8
  type Props<
18
9
  /**
@@ -54,109 +45,10 @@ type Props<
54
45
  * requirements can be placed in a React application in a manner that will
55
46
  * support server-side rendering and efficient caching.
56
47
  */
57
- export default class Data<TOptions, TData: ValidData> extends React.Component<
58
- Props<TOptions, TData>,
59
- > {
60
- _getHandlerFromInterceptor(
61
- interceptor: ?Interceptor,
62
- ): IRequestHandler<TOptions, TData> {
63
- const {handler} = this.props;
64
- if (!interceptor) {
65
- return handler;
66
- }
67
-
68
- const {fulfillRequest, shouldRefreshCache} = interceptor;
69
- const fulfillRequestFn = fulfillRequest
70
- ? (options: TOptions): Promise<TData> => {
71
- const interceptedResult = fulfillRequest(options);
72
- return interceptedResult != null
73
- ? interceptedResult
74
- : handler.fulfillRequest(options);
75
- }
76
- : (options) => handler.fulfillRequest(options);
77
- const shouldRefreshCacheFn = shouldRefreshCache
78
- ? (
79
- options: TOptions,
80
- cacheEntry: ?$ReadOnly<CacheEntry<TData>>,
81
- ): boolean => {
82
- const interceptedResult = shouldRefreshCache(
83
- options,
84
- cacheEntry,
85
- );
86
- return interceptedResult != null
87
- ? interceptedResult
88
- : handler.shouldRefreshCache(options, cacheEntry);
89
- }
90
- : (options, cacheEntry) =>
91
- handler.shouldRefreshCache(options, cacheEntry);
92
-
93
- return {
94
- fulfillRequest: fulfillRequestFn,
95
- shouldRefreshCache: shouldRefreshCacheFn,
96
- getKey: (options) => handler.getKey(options),
97
- type: handler.type,
98
- cache: handler.cache,
99
- hydrate: handler.hydrate,
100
- };
101
- }
102
-
103
- _getCacheLookupFnFromInterceptor(
104
- interceptor: ?Interceptor,
105
- ): $PropertyType<ResponseCache, "getEntry"> {
106
- const getEntry = interceptor && interceptor.getEntry;
107
- if (!getEntry) {
108
- return ResponseCache.Default.getEntry;
109
- }
110
-
111
- return <TOptions, TData: ValidData>(
112
- handler: IRequestHandler<TOptions, TData>,
113
- options: TOptions,
114
- ): ?$ReadOnly<CacheEntry<TData>> => {
115
- // 1. Lookup the current cache value.
116
- const cacheEntry = ResponseCache.Default.getEntry<TOptions, TData>(
117
- handler,
118
- options,
119
- );
120
-
121
- // 2. See if our interceptor wants to override it.
122
- const interceptedData = getEntry(options, cacheEntry);
123
-
124
- // 3. Return the appropriate response.
125
- return interceptedData != null ? interceptedData : cacheEntry;
126
- };
127
- }
128
-
129
- render(): React.Node {
130
- return (
131
- <InterceptContext.Consumer>
132
- {(value) => {
133
- const handlerType = this.props.handler.type;
134
- const interceptor = value[handlerType];
135
- const handler =
136
- this._getHandlerFromInterceptor(interceptor);
137
- const getEntry =
138
- this._getCacheLookupFnFromInterceptor(interceptor);
139
-
140
- /**
141
- * Need to share our types with InternalData so Flow
142
- * doesn't need to infer them and find mismatches.
143
- * However, just deriving a new component creates issues
144
- * where InternalData starts rerendering too often.
145
- * Couldn't track down why, so suppressing the error
146
- * instead.
147
- */
148
- return (
149
- <InternalData
150
- // $FlowIgnore[incompatible-type-arg]
151
- handler={handler}
152
- options={this.props.options}
153
- getEntry={getEntry}
154
- >
155
- {(result) => this.props.children(result)}
156
- </InternalData>
157
- );
158
- }}
159
- </InterceptContext.Consumer>
160
- );
161
- }
162
- }
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);
53
+ };
54
+ export default Data;
@@ -3,34 +3,36 @@ most folks will use. It describes a data requirement in terms of a handler, and
3
3
  some options. Handlers must implement the
4
4
  `IRequestHandler` interface.
5
5
 
6
- Among other things, the handler is responsible for fulfilling the request when
7
- asked to do so.
6
+ The handler is responsible for fulfilling the request when asked to do so.
8
7
 
9
- #### Caching
8
+ #### Server-side Rendering and Hydration
10
9
 
11
- The Wonder Blocks Data framework utilizes a core internal in-memory cache for
12
- supporting server-side rendering (SSR) and client-side provision of data
13
- obtained during SSR to support hydration of the SSR result.
10
+ The Wonder Blocks Data framework uses an in-memory cache for supporting
11
+ server-side rendering (SSR) and hydration.
14
12
 
15
- In addition to this internal cache, a custom cache can be provided by a request
16
- handler. Custom caches allow for different client-side caching
17
- strategies (such as using local storage instead of memory). Any custom cache
18
- provided is ignored during SSR.
13
+ ##### Server-side behavior
19
14
 
20
- When retrieving data from cache, the framework will ask the custom cache for its
21
- entry. If the custom cache returns an entry, that is used. If the custom cache
22
- returns `null`, the framework will look for a corresponding in-memory entry with
23
- which the framework has been initialzied, and if there, store the entry in the
24
- custom cache and then return it.
15
+ ###### Cache miss
25
16
 
26
- `removeFromCache` and `removeAllFromCache` methods are also provided for
27
- removing values from the in-memory and custom caches.
17
+ When the `Data` component does not get data or an error from the cache and it
18
+ is rendering server-side, it tells our request tracking that it wants data, and
19
+ it renders in its `loading` state. It will always render in this state if there
20
+ is no cached response.
28
21
 
29
- #### Client-side behavior
22
+ ###### Cache hit
30
23
 
31
- ##### Cache Miss
24
+ When the `Data` component gets data or an error from the cache and it is
25
+ rendering server-side, it will render as loaded, with that data or error,
26
+ as it would client-side. In this situation, it does not track the request it
27
+ would have made, as it already has the data and doesn't need to.
32
28
 
33
- When the cache does not yet contain the data, the data must be requested.
29
+
30
+ ##### Client-side behavior
31
+
32
+ ###### Cache miss
33
+
34
+ When the hydration cache does not contain the data, the data will be requested.
35
+ While the request is pending, the data is rendered in the loading state.
34
36
  In this example, we use a 3 second delayed promise to simulate the request.
35
37
  We start out without any data and so the request is made. Upon receipt of that
36
38
  data or an error, we re-render.
@@ -103,21 +105,16 @@ const invalid = new MyInvalidHandler();
103
105
  </View>
104
106
  ```
105
107
 
106
- ##### Cache Hit
107
-
108
- If the cache already contains data or an error for our request, then the `Data`
109
- component will render it immediately. The cache data is placed there either
110
- by prior successful requests as in the above Cache Miss example, or via calling
111
- `initializeCache` before any requests have been made. A cache hit may also
112
- occur due to the use of the `InterceptCache` component.
108
+ ###### Cache hit
113
109
 
114
- For the example below, we called `initializeData` data in our examples for
115
- that method. That way we'd have data ready for us here!
110
+ If the hydration cache already contains data or an error for our request, then
111
+ the `Data` component will render it immediately. The hydration cache is
112
+ populated using the `initializeCache` method before rendering.
116
113
 
117
114
  ```jsx
118
115
  import {Body, BodyMonospace} from "@khanacademy/wonder-blocks-typography";
119
116
  import {View} from "@khanacademy/wonder-blocks-core";
120
- import {InterceptCache, Data, RequestHandler} from "@khanacademy/wonder-blocks-data";
117
+ import {Data, RequestHandler, initializeCache} from "@khanacademy/wonder-blocks-data";
121
118
  import {Strut} from "@khanacademy/wonder-blocks-layout";
122
119
  import Color from "@khanacademy/wonder-blocks-color";
123
120
  import Spacing from "@khanacademy/wonder-blocks-spacing";
@@ -135,24 +132,21 @@ class MyHandler extends RequestHandler {
135
132
  "If you're seeing this error, the examples are broken and data isn't in the cache that should be.",
136
133
  );
137
134
  }
138
-
139
- shouldRefreshCache(options, cachedEntry) {
140
- /**
141
- * For our purposes, the cache never needs a refresh.
142
- */
143
- return false;
144
- }
145
135
  }
146
136
 
147
137
  const handler = new MyHandler();
148
- const getEntryInterceptor = function(options) {
149
- if (options === "DATA") {
150
- return { data: "I'm DATA from the cache" };
138
+ 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
+ }
151
146
  }
152
- return {error: "I'm an ERROR from the cache" };
153
- };
147
+ });
154
148
 
155
- <InterceptCache handler={handler} getEntry={getEntryInterceptor}>
149
+ <View>
156
150
  <View>
157
151
  <Body>This cache has data!</Body>
158
152
  <Data handler={handler} options={"DATA"}>
@@ -182,21 +176,5 @@ const getEntryInterceptor = function(options) {
182
176
  }}
183
177
  </Data>
184
178
  </View>
185
- </InterceptCache>
179
+ </View>
186
180
  ```
187
-
188
- #### Server-side behavior
189
-
190
- ##### Cache miss
191
-
192
- When the `Data` component does not get data or an error from the cache and it
193
- is rendering server-side, it tells our request tracking that it wants data, and
194
- it renders in its `loading` state. It will always render in this state if there
195
- is no cached response.
196
-
197
- ##### Cache hit
198
-
199
- When the `Data` component gets data or an error from the cache and it is
200
- rendering server-side, it will render as loaded, with that data or error,
201
- as it would client-side. In this situation, it does not track the request it
202
- would have made, as it already has the data and doesn't need to.
@@ -0,0 +1,66 @@
1
+ // @flow
2
+ import * as React from "react";
3
+
4
+ import {GqlRouterContext} from "../util/gql-router-context.js";
5
+
6
+ import type {
7
+ GqlContext,
8
+ GqlFetchFn,
9
+ GqlRouterConfiguration,
10
+ } from "../util/gql-types.js";
11
+
12
+ type Props<TContext: GqlContext> = {|
13
+ /**
14
+ * The default context to be used by operations when no context is provided.
15
+ */
16
+ defaultContext: TContext,
17
+
18
+ /**
19
+ * The function to use when fetching requests.
20
+ */
21
+ fetch: GqlFetchFn<any, any, any, TContext>,
22
+
23
+ /**
24
+ * The children to be rendered inside the router.
25
+ */
26
+ children: React.Node,
27
+ |};
28
+
29
+ /**
30
+ * Configure GraphQL routing for GraphQL hooks and components.
31
+ *
32
+ * These can be nested. Components and hooks relying on the GraphQL routing
33
+ * will use the configuration from their closest ancestral GqlRouter.
34
+ */
35
+ export const GqlRouter = <TContext: GqlContext>({
36
+ defaultContext: thisDefaultContext,
37
+ fetch: thisFetch,
38
+ children,
39
+ }: Props<TContext>): React.Node => {
40
+ // We don't care if we're nested. We always force our callers to define
41
+ // everything. It makes for a clearer API and requires less error checking
42
+ // code (assuming our flow types are correct). We also don't default fetch
43
+ // to anything - our callers can tell us what function to use quite easily.
44
+ // If code that consumes this wants more nuanced nesting, it can implement
45
+ // it within its own GqlRouter than then defers to this one.
46
+
47
+ // We want to always use the same object if things haven't changed to avoid
48
+ // over-rendering consumers of our context, let's memoize the configuration.
49
+ // By doing this, if a component under children that uses this context
50
+ // uses React.memo, we won't force it to re-render every time we render
51
+ // because we'll only change the context value if something has actually
52
+ // changed.
53
+ const configuration: GqlRouterConfiguration<TContext> = React.useMemo(
54
+ () => ({
55
+ fetch: thisFetch,
56
+ defaultContext: thisDefaultContext,
57
+ }),
58
+ [thisDefaultContext, thisFetch],
59
+ );
60
+
61
+ return (
62
+ <GqlRouterContext.Provider value={configuration}>
63
+ {children}
64
+ </GqlRouterContext.Provider>
65
+ );
66
+ };
@@ -7,10 +7,9 @@ import type {
7
7
  ValidData,
8
8
  IRequestHandler,
9
9
  InterceptFulfillRequestFn,
10
- InterceptShouldRefreshCacheFn,
11
10
  } from "../util/types.js";
12
11
 
13
- type BaseProps<TOptions, TData> = {|
12
+ type Props<TOptions, TData> = {|
14
13
  /**
15
14
  * A handler of the type to be intercepted.
16
15
  */
@@ -24,9 +23,7 @@ type BaseProps<TOptions, TData> = {|
24
23
  * one).
25
24
  */
26
25
  children: React.Node,
27
- |};
28
26
 
29
- type FulfillRequestProps<TOptions, TData> = {|
30
27
  /**
31
28
  * Called to fulfill a request.
32
29
  * If this returns null, the request will be fulfilled by the
@@ -35,38 +32,11 @@ type FulfillRequestProps<TOptions, TData> = {|
35
32
  fulfillRequest: InterceptFulfillRequestFn<TOptions, TData>,
36
33
  |};
37
34
 
38
- type ShouldRefreshCacheProps<TOptions, TData> = {|
39
- /**
40
- * Called to determine if the cache should be refreshed.
41
- * If this returns null, the handler being intercepted will be asked if
42
- * the cache should be refreshed.
43
- */
44
- shouldRefreshCache: InterceptShouldRefreshCacheFn<TOptions, TData>,
45
- |};
46
-
47
- type Props<TOptions, TData> =
48
- | {|
49
- ...BaseProps<TOptions, TData>,
50
- ...FulfillRequestProps<TOptions, TData>,
51
- ...ShouldRefreshCacheProps<TOptions, TData>,
52
- |}
53
- | {|
54
- ...BaseProps<TOptions, TData>,
55
- ...FulfillRequestProps<TOptions, TData>,
56
- |}
57
- | {|
58
- ...BaseProps<TOptions, TData>,
59
- ...ShouldRefreshCacheProps<TOptions, TData>,
60
- |};
61
-
62
35
  /**
63
36
  * This component provides a mechanism to intercept the data requests for the
64
37
  * type of a given handler and provide alternative results. This is mostly
65
38
  * useful for testing.
66
39
  *
67
- * Results from this interceptor will end up in the cache. If you
68
- * wish to only override the cache, use `InterceptCache` instead.
69
- *
70
40
  * This component is not recommended for use in production code as it
71
41
  * can prevent predictable functioning of the Wonder Blocks Data framework.
72
42
  * One possible side-effect is that inflight requests from the interceptor could
@@ -89,9 +59,7 @@ export default class InterceptData<
89
59
  const handlerType = this.props.handler.type;
90
60
  const interceptor = {
91
61
  ...value[handlerType],
92
- fulfillRequest: this.props.fulfillRequest || null,
93
- shouldRefreshCache:
94
- this.props.shouldRefreshCache || null,
62
+ fulfillRequest: this.props.fulfillRequest,
95
63
  };
96
64
  const newValue = {
97
65
  ...value,