@samitouri / QOS-React-2 / commits / a317bd033f

Flip the arguments of Blocks and make the query optional (#18374)

* Flip the arguments of Blocks and make the query optional * Rename Query to Load

Sebastian Markbåge committed Mar 24, 2020 at 10:58 UTC a317bd033fbb5980f5aa4c6be91fb0040dd37523
7 files changed +116 -44
packages/react-client/src/ReactFlightClient.js
+1 -1
@@ -152,7 +152,7 @@ export function reportGlobalError(response: Response, error: Error): void {
152 }
153
154 function readMaybeChunk<T>(maybeChunk: Chunk<T> | T): T {
155 - if ((maybeChunk: any).$$typeof !== CHUNK_TYPE) {
155 + if (maybeChunk == null || (maybeChunk: any).$$typeof !== CHUNK_TYPE) {
156 // $FlowFixMe
157 return maybeChunk;
158 }
packages/react-client/src/__tests__/ReactFlight-test.js
+34 -7
@@ -29,12 +29,15 @@ describe('ReactFlight', () => {
29 act = ReactNoop.act;
30 });
31
32 - function block(query, render) {
32 + function block(render, load) {
33 return function(...args) {
34 - let curriedQuery = () => {
35 - return query(...args);
34 + if (load === undefined) {
35 + return [Symbol.for('react.server.block'), render];
36 + }
37 + let curriedLoad = () => {
38 + return load(...args);
39 };
37 - return [Symbol.for('react.server.block'), render, curriedQuery];
40 + return [Symbol.for('react.server.block'), render, curriedLoad];
41 };
42 }
43
@@ -70,8 +73,32 @@ describe('ReactFlight', () => {
73 });
74
75 if (ReactFeatureFlags.enableBlocksAPI) {
73 - it('can transfer a Block to the client and render there', () => {
74 - function Query(firstName, lastName) {
76 + it('can transfer a Block to the client and render there, without data', () => {
77 + function User(props, data) {
78 + return (
79 + <span>
80 + {props.greeting} {typeof data}
81 + </span>
82 + );
83 + }
84 + let loadUser = block(User);
85 + let model = {
86 + User: loadUser('Seb', 'Smith'),
87 + };
88 +
89 + let transport = ReactNoopFlightServer.render(model);
90 + let root = ReactNoopFlightClient.read(transport);
91 +
92 + act(() => {
93 + let UserClient = root.model.User;
94 + ReactNoop.render(<UserClient greeting="Hello" />);
95 + });
96 +
97 + expect(ReactNoop).toMatchRenderedOutput(<span>Hello undefined</span>);
98 + });
99 +
100 + it('can transfer a Block to the client and render there, with data', () => {
101 + function load(firstName, lastName) {
102 return {name: firstName + ' ' + lastName};
103 }
104 function User(props, data) {
@@ -81,7 +108,7 @@ describe('ReactFlight', () => {
108 </span>
109 );
110 }
84 - let loadUser = block(Query, User);
111 + let loadUser = block(User, load);
112 let model = {
113 User: loadUser('Seb', 'Smith'),
114 };
packages/react-flight-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js
+9 -6
@@ -44,12 +44,15 @@ describe('ReactFlightDOMRelay', () => {
44 return model;
45 }
46
47 - function block(query, render) {
47 + function block(render, load) {
48 return function(...args) {
49 - let curriedQuery = () => {
50 - return query(...args);
49 + if (load === undefined) {
50 + return [Symbol.for('react.server.block'), render];
51 + }
52 + let curriedLoad = () => {
53 + return load(...args);
54 };
52 - return [Symbol.for('react.server.block'), render, curriedQuery];
55 + return [Symbol.for('react.server.block'), render, curriedLoad];
56 };
57 }
58
@@ -89,7 +92,7 @@ describe('ReactFlightDOMRelay', () => {
92 });
93
94 it.experimental('can transfer a Block to the client and render there', () => {
92 - function Query(firstName, lastName) {
95 + function load(firstName, lastName) {
96 return {name: firstName + ' ' + lastName};
97 }
98 function User(props, data) {
@@ -99,7 +102,7 @@ describe('ReactFlightDOMRelay', () => {
102 </span>
103 );
104 }
102 - let loadUser = block(Query, User);
105 + let loadUser = block(User, load);
106 let model = {
107 User: loadUser('Seb', 'Smith'),
108 };
packages/react-flight-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+10 -7
@@ -62,7 +62,7 @@ describe('ReactFlightDOM', () => {
62 };
63 }
64
65 - function block(query, render) {
65 + function block(render, load) {
66 let idx = webpackModuleIdx++;
67 webpackModules[idx] = {
68 d: render,
@@ -73,10 +73,13 @@ describe('ReactFlightDOM', () => {
73 name: 'd',
74 };
75 return function(...args) {
76 - let curriedQuery = () => {
77 - return query(...args);
76 + if (load === undefined) {
77 + return [Symbol.for('react.server.block'), render];
78 + }
79 + let curriedLoad = () => {
80 + return load(...args);
81 };
79 - return [Symbol.for('react.server.block'), 'path/' + idx, curriedQuery];
82 + return [Symbol.for('react.server.block'), 'path/' + idx, curriedLoad];
83 };
84 }
85
@@ -288,7 +291,7 @@ describe('ReactFlightDOM', () => {
291 reject(e);
292 };
293 });
291 - function Query() {
294 + function load() {
295 if (promise) {
296 throw promise;
297 }
@@ -300,8 +303,8 @@ describe('ReactFlightDOM', () => {
303 function DelayedText({children}, data) {
304 return <Text>{children}</Text>;
305 }
303 - let _block = block(Query, DelayedText);
304 - return [_block(), _resolve, _reject];
306 + let loadBlock = block(DelayedText, load);
307 + return [loadBlock(), _resolve, _reject];
308 }
309
310 const [FriendsModel, resolveFriendsModel] = makeDelayedText();
packages/react-reconciler/src/__tests__/ReactBlocks-test.js
+31 -9
@@ -49,8 +49,30 @@ describe('ReactBlocks', () => {
49 };
50 });
51
52 + it.experimental('renders a simple component', () => {
53 + function User(props, data) {
54 + return <div>{typeof data}</div>;
55 + }
56 +
57 + function App({Component}) {
58 + return (
59 + <Suspense fallback={'Loading...'}>
60 + <Component name="Name" />
61 + </Suspense>
62 + );
63 + }
64 +
65 + let loadUser = block(User);
66 +
67 + ReactNoop.act(() => {
68 + ReactNoop.render(<App Component={loadUser()} />);
69 + });
70 +
71 + expect(ReactNoop).toMatchRenderedOutput(<div>undefined</div>);
72 + });
73 +
74 it.experimental('prints the name of the render function in warnings', () => {
53 - function Query(firstName) {
75 + function load(firstName) {
76 return {
77 name: firstName,
78 };
@@ -69,7 +91,7 @@ describe('ReactBlocks', () => {
91 );
92 }
93
72 - let loadUser = block(Query, User);
94 + let loadUser = block(User, load);
95
96 expect(() => {
97 ReactNoop.act(() => {
@@ -86,8 +108,8 @@ describe('ReactBlocks', () => {
108 );
109 });
110
89 - it.experimental('renders a component with a suspending query', async () => {
90 - function Query(id) {
111 + it.experimental('renders a component with a suspending load', async () => {
112 + function load(id) {
113 return {
114 id: id,
115 name: readString('Sebastian'),
@@ -102,7 +124,7 @@ describe('ReactBlocks', () => {
124 );
125 }
126
105 - let loadUser = block(Query, Render);
127 + let loadUser = block(Render, load);
128
129 function App({User}) {
130 return (
@@ -128,7 +150,7 @@ describe('ReactBlocks', () => {
150 it.experimental(
151 'does not support a lazy wrapper around a chunk',
152 async () => {
131 - function Query(id) {
153 + function load(id) {
154 return {
155 id: id,
156 name: readString('Sebastian'),
@@ -143,7 +165,7 @@ describe('ReactBlocks', () => {
165 );
166 }
167
146 - let loadUser = block(Query, Render);
168 + let loadUser = block(Render, load);
169
170 function App({User}) {
171 return (
@@ -187,7 +209,7 @@ describe('ReactBlocks', () => {
209 it.experimental(
210 'can receive updated data for the same component',
211 async () => {
190 - function Query(firstName) {
212 + function load(firstName) {
213 return {
214 name: firstName,
215 };
@@ -203,7 +225,7 @@ describe('ReactBlocks', () => {
225 );
226 }
227
206 - let loadUser = block(Query, Render);
228 + let loadUser = block(Render, load);
229
230 function App({User}) {
231 return (
packages/react-server/src/ReactFlightServer.js
+6 -6
@@ -191,11 +191,11 @@ export function resolveModelToJSON(
191 }
192 }
193 case '2': {
194 - // Query
195 - let query: () => ReactModel = (value: any);
194 + // Load function
195 + let load: () => ReactModel = (value: any);
196 try {
197 - // Attempt to resolve the query.
198 - return query();
197 + // Attempt to resolve the data.
198 + return load();
199 } catch (x) {
200 if (
201 typeof x === 'object' &&
@@ -204,12 +204,12 @@ export function resolveModelToJSON(
204 ) {
205 // Something suspended, we'll need to create a new segment and resolve it later.
206 request.pendingChunks++;
207 - let newSegment = createSegment(request, query);
207 + let newSegment = createSegment(request, load);
208 let ping = newSegment.ping;
209 x.then(ping, ping);
210 return serializeIDRef(newSegment.id);
211 } else {
212 - // This query failed, encode the error as a separate row and reference that.
212 + // This load failed, encode the error as a separate row and reference that.
213 request.pendingChunks++;
214 let errorId = request.nextChunkId++;
215 emitErrorChunk(request, errorId, x);
packages/react/src/ReactBlock.js
+25 -8
@@ -16,14 +16,14 @@ import {
16 REACT_FORWARD_REF_TYPE,
17 } from 'shared/ReactSymbols';
18
19 -type BlockQueryFunction<Args: Iterable<any>, Data> = (...args: Args) => Data;
19 +type BlockLoadFunction<Args: Iterable<any>, Data> = (...args: Args) => Data;
20 export type BlockRenderFunction<Props, Data> = (
21 props: Props,
22 data: Data,
23 ) => React$Node;
24
25 type Payload<Props, Args: Iterable<any>, Data> = {
26 - query: BlockQueryFunction<Args, Data>,
26 + load: BlockLoadFunction<Args, Data>,
27 args: Args,
28 render: BlockRenderFunction<Props, Data>,
29 };
@@ -44,20 +44,20 @@ function lazyInitializer<Props, Args: Iterable<any>, Data>(
44 ): BlockComponent<Props, Data> {
45 return {
46 $$typeof: REACT_BLOCK_TYPE,
47 - _data: payload.query.apply(null, payload.args),
47 + _data: payload.load.apply(null, payload.args),
48 _render: payload.render,
49 };
50 }
51
52 export function block<Args: Iterable<any>, Props, Data>(
53 - query: BlockQueryFunction<Args, Data>,
53 render: BlockRenderFunction<Props, Data>,
54 + load?: BlockLoadFunction<Args, Data>,
55 ): (...args: Args) => Block<Props> {
56 if (__DEV__) {
57 - if (typeof query !== 'function') {
57 + if (load !== undefined && typeof load !== 'function') {
58 console.error(
59 - 'Blocks require a query function but was given %s.',
60 - query === null ? 'null' : typeof query,
59 + 'Blocks require a load function, if provided, but was given %s.',
60 + load === null ? 'null' : typeof load,
61 );
62 }
63 if (render != null && render.$$typeof === REACT_MEMO_TYPE) {
@@ -97,11 +97,28 @@ export function block<Args: Iterable<any>, Props, Data>(
97 }
98 }
99
100 + if (load === undefined) {
101 + return function(): Block<Props> {
102 + let blockComponent: BlockComponent<Props, void> = {
103 + $$typeof: REACT_BLOCK_TYPE,
104 + _data: undefined,
105 + // $FlowFixMe: Data must be void in this scenario.
106 + _render: render,
107 + };
108 +
109 + // $FlowFixMe
110 + return blockComponent;
111 + };
112 + }
113 +
114 + // Trick to let Flow refine this.
115 + let loadFn = load;
116 +
117 return function(): Block<Props> {
118 let args: Args = arguments;
119
120 let payload: Payload<Props, Args, Data> = {
104 - query: query,
121 + load: loadFn,
122 args: args,
123 render: render,
124 };