@samitouri / QOS-React / commits / c1ac052158

[Flight] Support more element types and Hooks for Server and Hybrid Components (#19711)

* Shim support for more element types * Shim commonly used Hooks that are safe * Flow * Oopsie

Dan Abramov committed Aug 27, 2020 at 20:19 UTC c1ac052158ee9ec29d3e748420d9d577b8b7412d
3 files changed +166 -5
packages/react-server/src/ReactFlightServer.js
+75 -4
@@ -7,6 +7,7 @@
7 * @flow
8 */
9
10 +import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
11 import type {
12 Destination,
13 Chunk,
@@ -29,12 +30,24 @@ import {
30
31 import {
32 REACT_BLOCK_TYPE,
32 - REACT_SERVER_BLOCK_TYPE,
33 REACT_ELEMENT_TYPE,
34 + REACT_DEBUG_TRACING_MODE_TYPE,
35 + REACT_FORWARD_REF_TYPE,
36 REACT_FRAGMENT_TYPE,
37 REACT_LAZY_TYPE,
38 + REACT_LEGACY_HIDDEN_TYPE,
39 + REACT_MEMO_TYPE,
40 + REACT_OFFSCREEN_TYPE,
41 + REACT_PROFILER_TYPE,
42 + REACT_SCOPE_TYPE,
43 + REACT_SERVER_BLOCK_TYPE,
44 + REACT_STRICT_MODE_TYPE,
45 + REACT_SUSPENSE_TYPE,
46 + REACT_SUSPENSE_LIST_TYPE,
47 } from 'shared/ReactSymbols';
48
49 +import * as React from 'react';
50 +import ReactSharedInternals from 'shared/ReactSharedInternals';
51 import invariant from 'shared/invariant';
52
53 type ReactJSONValue =
@@ -74,6 +87,8 @@ export type Request = {
87 toJSON: (key: string, value: ReactModel) => ReactJSONValue,
88 };
89
90 +const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
91 +
92 export function createRequest(
93 model: ReactModel,
94 destination: Destination,
@@ -110,11 +125,33 @@ function attemptResolveElement(element: React$Element<any>): ReactModel {
125 return [REACT_ELEMENT_TYPE, type, element.key, element.props];
126 } else if (type[0] === REACT_SERVER_BLOCK_TYPE) {
127 return [REACT_ELEMENT_TYPE, type, element.key, element.props];
113 - } else if (type === REACT_FRAGMENT_TYPE) {
128 + } else if (
129 + type === REACT_FRAGMENT_TYPE ||
130 + type === REACT_STRICT_MODE_TYPE ||
131 + type === REACT_PROFILER_TYPE ||
132 + type === REACT_SCOPE_TYPE ||
133 + type === REACT_DEBUG_TRACING_MODE_TYPE ||
134 + type === REACT_LEGACY_HIDDEN_TYPE ||
135 + type === REACT_OFFSCREEN_TYPE ||
136 + // TODO: These are temporary shims
137 + // and we'll want a different behavior.
138 + type === REACT_SUSPENSE_TYPE ||
139 + type === REACT_SUSPENSE_LIST_TYPE
140 + ) {
141 return element.props.children;
115 - } else {
116 - invariant(false, 'Unsupported type.');
142 + } else if (type != null && typeof type === 'object') {
143 + switch (type.$$typeof) {
144 + case REACT_FORWARD_REF_TYPE: {
145 + const render = type.render;
146 + return render(props, undefined);
147 + }
148 + case REACT_MEMO_TYPE: {
149 + const nextChildren = React.createElement(type.type, element.props);
150 + return attemptResolveElement(nextChildren);
151 + }
152 + }
153 }
154 + invariant(false, 'Unsupported type.');
155 }
156
157 function pingSegment(request: Request, segment: Segment): void {
@@ -236,9 +273,11 @@ export function resolveModelToJSON(
273 value !== null &&
274 value.$$typeof === REACT_ELEMENT_TYPE
275 ) {
276 + const prevDispatcher = ReactCurrentDispatcher.current;
277 // TODO: Concatenate keys of parents onto children.
278 const element: React$Element<any> = (value: any);
279 try {
280 + ReactCurrentDispatcher.current = Dispatcher;
281 // Attempt to render the server component.
282 value = attemptResolveElement(element);
283 } catch (x) {
@@ -253,6 +292,8 @@ export function resolveModelToJSON(
292 // Something errored. Don't bother encoding anything up to here.
293 throw x;
294 }
295 + } finally {
296 + ReactCurrentDispatcher.current = prevDispatcher;
297 }
298 }
299
@@ -378,3 +419,33 @@ export function startFlowing(request: Request): void {
419 request.flowing = true;
420 flushCompletedChunks(request);
421 }
422 +
423 +function unsupportedHook(): void {
424 + invariant(false, 'This Hook is not supported in Server Components.');
425 +}
426 +
427 +const Dispatcher: DispatcherType = {
428 + useMemo<T>(nextCreate: () => T): T {
429 + return nextCreate();
430 + },
431 + useCallback<T>(callback: T): T {
432 + return callback;
433 + },
434 + useDebugValue(): void {},
435 + useDeferredValue<T>(value: T): T {
436 + return value;
437 + },
438 + useTransition(): [(callback: () => void) => void, boolean] {
439 + return [() => {}, false];
440 + },
441 + readContext: (unsupportedHook: any),
442 + useContext: (unsupportedHook: any),
443 + useReducer: (unsupportedHook: any),
444 + useRef: (unsupportedHook: any),
445 + useState: (unsupportedHook: any),
446 + useLayoutEffect: (unsupportedHook: any),
447 + useImperativeHandle: (unsupportedHook: any),
448 + useEffect: (unsupportedHook: any),
449 + useOpaqueIdentifier: (unsupportedHook: any),
450 + useMutableSource: (unsupportedHook: any),
451 +};
packages/react-transport-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js
+89
@@ -124,4 +124,93 @@ describe('ReactFlightDOMRelay', () => {
124
125 expect(container.innerHTML).toEqual('<span>Hello, Seb Smith</span>');
126 });
127 +
128 + // @gate experimental
129 + it('can reasonably handle different element types', () => {
130 + const {
131 + forwardRef,
132 + memo,
133 + Fragment,
134 + StrictMode,
135 + Profiler,
136 + Suspense,
137 + SuspenseList,
138 + } = React;
139 +
140 + const Inner = memo(
141 + forwardRef((props, ref) => {
142 + return <div ref={ref}>{'Hello ' + props.name}</div>;
143 + }),
144 + );
145 +
146 + function Foo() {
147 + return {
148 + bar: (
149 + <div>
150 + <Fragment>Fragment child</Fragment>
151 + <Profiler>Profiler child</Profiler>
152 + <StrictMode>StrictMode child</StrictMode>
153 + <Suspense fallback="Loading...">Suspense child</Suspense>
154 + <SuspenseList fallback="Loading...">
155 + {'SuspenseList row 1'}
156 + {'SuspenseList row 2'}
157 + </SuspenseList>
158 + <Inner name="world" />
159 + </div>
160 + ),
161 + };
162 + }
163 + const transport = [];
164 + ReactDOMFlightRelayServer.render(
165 + {
166 + foo: <Foo />,
167 + },
168 + transport,
169 + );
170 +
171 + const model = readThrough(transport);
172 + expect(model).toEqual({
173 + foo: {
174 + bar: (
175 + <div>
176 + {'Fragment child'}
177 + {'Profiler child'}
178 + {'StrictMode child'}
179 + {'Suspense child'}
180 + {['SuspenseList row 1', 'SuspenseList row 2']}
181 + <div>Hello world</div>
182 + </div>
183 + ),
184 + },
185 + });
186 + });
187 +
188 + it('can handle a subset of Hooks', () => {
189 + const {useMemo, useCallback} = React;
190 + function Inner({x}) {
191 + const foo = useMemo(() => x + x, [x]);
192 + const bar = useCallback(() => 10 + foo, [foo]);
193 + return bar();
194 + }
195 +
196 + function Foo() {
197 + return {
198 + bar: <Inner x={2} />,
199 + };
200 + }
201 + const transport = [];
202 + ReactDOMFlightRelayServer.render(
203 + {
204 + foo: <Foo />,
205 + },
206 + transport,
207 + );
208 +
209 + const model = readThrough(transport);
210 + expect(model).toEqual({
211 + foo: {
212 + bar: 14,
213 + },
214 + });
215 + });
216 });
scripts/error-codes/codes.json
+2 -1
@@ -360,5 +360,6 @@
360 "369": "ReactDOM.createEventHandle: setter called on an invalid target. Provide a valid EventTarget or an element managed by React.",
361 "370": "ReactDOM.createEventHandle: setter called with an invalid callback. The callback must be a function.",
362 "371": "Text string must be rendered within a <Text> component.\n\nText: %s",
363 - "372": "Cannot call unstable_createEventHandle with \"%s\", as it is not an event known to React."
363 + "372": "Cannot call unstable_createEventHandle with \"%s\", as it is not an event known to React.",
364 + "373": "This Hook is not supported in Server Components."
365 }