Skip to content

Commit c1ac052

Browse files
authored
[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
1 parent 1eaafc9 commit c1ac052

File tree

3 files changed

+166
-5
lines changed

3 files changed

+166
-5
lines changed

packages/react-server/src/ReactFlightServer.js

+75-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* @flow
88
*/
99

10+
importtype{DispatcherasDispatcherType}from'react-reconciler/src/ReactInternalTypes';
1011
importtype{
1112
Destination,
1213
Chunk,
@@ -29,12 +30,24 @@ import {
2930

3031
import{
3132
REACT_BLOCK_TYPE,
32-
REACT_SERVER_BLOCK_TYPE,
3333
REACT_ELEMENT_TYPE,
34+
REACT_DEBUG_TRACING_MODE_TYPE,
35+
REACT_FORWARD_REF_TYPE,
3436
REACT_FRAGMENT_TYPE,
3537
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,
3647
}from'shared/ReactSymbols';
3748

49+
import*asReactfrom'react';
50+
importReactSharedInternalsfrom'shared/ReactSharedInternals';
3851
importinvariantfrom'shared/invariant';
3952

4053
typeReactJSONValue=
@@ -74,6 +87,8 @@ export type Request = {
7487
toJSON: (key: string,value: ReactModel)=>ReactJSONValue,
7588
};
7689

90+
constReactCurrentDispatcher=ReactSharedInternals.ReactCurrentDispatcher;
91+
7792
exportfunctioncreateRequest(
7893
model: ReactModel,
7994
destination: Destination,
@@ -110,11 +125,33 @@ function attemptResolveElement(element: React$Element<any>): ReactModel {
110125
return[REACT_ELEMENT_TYPE,type,element.key,element.props];
111126
}elseif(type[0]===REACT_SERVER_BLOCK_TYPE){
112127
return[REACT_ELEMENT_TYPE,type,element.key,element.props];
113-
}elseif(type===REACT_FRAGMENT_TYPE){
128+
}elseif(
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+
){
114141
returnelement.props.children;
115-
}else{
116-
invariant(false,'Unsupported type.');
142+
}elseif(type!=null&&typeoftype=== 'object'){
143+
switch(type.$$typeof){
144+
caseREACT_FORWARD_REF_TYPE: {
145+
constrender=type.render;
146+
returnrender(props,undefined);
147+
}
148+
caseREACT_MEMO_TYPE: {
149+
constnextChildren=React.createElement(type.type,element.props);
150+
returnattemptResolveElement(nextChildren);
151+
}
152+
}
117153
}
154+
invariant(false,'Unsupported type.');
118155
}
119156

120157
functionpingSegment(request: Request,segment: Segment): void{
@@ -236,9 +273,11 @@ export function resolveModelToJSON(
236273
value!==null&&
237274
value.$$typeof===REACT_ELEMENT_TYPE
238275
){
276+
constprevDispatcher=ReactCurrentDispatcher.current;
239277
// TODO: Concatenate keys of parents onto children.
240278
constelement: React$Element<any> = (value: any);
241279
try {
280+
ReactCurrentDispatcher.current=Dispatcher;
242281
// Attempt to render the server component.
243282
value=attemptResolveElement(element);
244283
} catch (x) {
@@ -253,6 +292,8 @@ export function resolveModelToJSON(
253292
// Something errored. Don't bother encoding anything up to here.
254293
throwx;
255294
}
295+
}finally{
296+
ReactCurrentDispatcher.current=prevDispatcher;
256297
}
257298
}
258299

@@ -378,3 +419,33 @@ export function startFlowing(request: Request): void {
378419
request.flowing=true;
379420
flushCompletedChunks(request);
380421
}
422+
423+
functionunsupportedHook(): void{
424+
invariant(false,'This Hook is not supported in Server Components.');
425+
}
426+
427+
constDispatcher: DispatcherType={
428+
useMemo<T>(nextCreate: ()=>T): T{
429+
returnnextCreate();
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
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,93 @@ describe('ReactFlightDOMRelay', () => {
124124

125125
expect(container.innerHTML).toEqual('<span>Hello, Seb Smith</span>');
126126
});
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+
constInner=memo(
141+
forwardRef((props,ref)=>{
142+
return<divref={ref}>{'Hello '+props.name}</div>;
143+
}),
144+
);
145+
146+
functionFoo(){
147+
return{
148+
bar: (
149+
<div>
150+
<Fragment>Fragment child</Fragment>
151+
<Profiler>Profiler child</Profiler>
152+
<StrictMode>StrictMode child</StrictMode>
153+
<Suspensefallback="Loading...">Suspense child</Suspense>
154+
<SuspenseListfallback="Loading...">
155+
{'SuspenseList row 1'}
156+
{'SuspenseList row 2'}
157+
</SuspenseList>
158+
<Innername="world"/>
159+
</div>
160+
),
161+
};
162+
}
163+
consttransport=[];
164+
ReactDOMFlightRelayServer.render(
165+
{
166+
foo: <Foo/>,
167+
},
168+
transport,
169+
);
170+
171+
constmodel=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+
functionInner({x}){
191+
constfoo=useMemo(()=>x+x,[x]);
192+
constbar=useCallback(()=>10+foo,[foo]);
193+
returnbar();
194+
}
195+
196+
functionFoo(){
197+
return{
198+
bar: <Innerx={2}/>,
199+
};
200+
}
201+
consttransport=[];
202+
ReactDOMFlightRelayServer.render(
203+
{
204+
foo: <Foo/>,
205+
},
206+
transport,
207+
);
208+
209+
constmodel=readThrough(transport);
210+
expect(model).toEqual({
211+
foo: {
212+
bar: 14,
213+
},
214+
});
215+
});
127216
});

scripts/error-codes/codes.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -360,5 +360,6 @@
360360
"369": "ReactDOM.createEventHandle: setter called on an invalid target. Provide a valid EventTarget or an element managed by React.",
361361
"370": "ReactDOM.createEventHandle: setter called with an invalid callback. The callback must be a function.",
362362
"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."
364365
}

0 commit comments

Comments
 (0)
close