experimental_use(context) for server components and ssr (#25226)
implements the experimental use(context) API for the server components (Flight) and SSR (Fizz) runtimes
mofeiZ committed
Sep 9, 2022 at 21:19 UTC
3613284dceac38de4ef5a7c93c4897600ee6923b
7 files changed
+107
-11
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+48
@@ -5292,6 +5292,54 @@ describe('ReactDOMFizzServer', () => {
5292
expect(getVisibleChildren(container)).toEqual('ABC');
5293
});
5294
5295
+ // @gate enableUseHook
5296
+ it('basic use(context)', async () => {
5297
+ const ContextA = React.createContext('default');
5298
+ const ContextB = React.createContext('B');
5299
+ const ServerContext = React.createServerContext(
5300
+ 'ServerContext',
5301
+ 'default',
5302
+ );
5303
+ function Client() {
5304
+ return use(ContextA) + use(ContextB);
5305
+ }
5306
+ function ServerComponent() {
5307
+ return use(ServerContext);
5308
+ }
5309
+ function Server() {
5310
+ return (
5311
+ <ServerContext.Provider value="C">
5312
+ <ServerComponent />
5313
+ </ServerContext.Provider>
5314
+ );
5315
+ }
5316
+ function App() {
5317
+ return (
5318
+ <>
5319
+ <ContextA.Provider value="A">
5320
+ <Client />
5321
+ </ContextA.Provider>
5322
+ <Server />
5323
+ </>
5324
+ );
5325
+ }
5326
+
5327
+ await act(async () => {
5328
+ const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
5329
+ pipe(writable);
5330
+ });
5331
+ expect(getVisibleChildren(container)).toEqual(['AB', 'C']);
5332
+
5333
+ // Hydration uses a different renderer runtime (Fiber instead of Fizz).
5334
+ // We reset _currentRenderer here to not trigger a warning about multiple
5335
+ // renderers concurrently using these contexts
5336
+ ContextA._currentRenderer = null;
5337
+ ServerContext._currentRenderer = null;
5338
+ ReactDOMClient.hydrateRoot(container, <App />);
5339
+ expect(Scheduler).toFlushAndYield([]);
5340
+ expect(getVisibleChildren(container)).toEqual(['AB', 'C']);
5341
+ });
5342
+
5343
// @gate enableUseHook
5344
it('use(promise) in multiple components', async () => {
5345
const promiseA = Promise.resolve('A');
packages/react-reconciler/src/ReactFiberHooks.new.js
+6
-3
@@ -36,7 +36,10 @@ import {
36
enableUseHook,
37
enableUseMemoCacheHook,
38
} from 'shared/ReactFeatureFlags';
39
-import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
39
+import {
40
+ REACT_CONTEXT_TYPE,
41
+ REACT_SERVER_CONTEXT_TYPE,
42
+} from 'shared/ReactSymbols';
43
44
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
45
import {
@@ -771,8 +774,8 @@ function use<T>(usable: Usable<T>): T {
774
}
775
}
776
} else if (
774
- usable.$$typeof != null &&
775
- usable.$$typeof === REACT_CONTEXT_TYPE
777
+ usable.$$typeof === REACT_CONTEXT_TYPE ||
778
+ usable.$$typeof === REACT_SERVER_CONTEXT_TYPE
779
) {
780
const context: ReactContext<T> = (usable: any);
781
return readContext(context);
packages/react-reconciler/src/ReactFiberHooks.old.js
+6
-3
@@ -36,7 +36,10 @@ import {
36
enableUseHook,
37
enableUseMemoCacheHook,
38
} from 'shared/ReactFeatureFlags';
39
-import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
39
+import {
40
+ REACT_CONTEXT_TYPE,
41
+ REACT_SERVER_CONTEXT_TYPE,
42
+} from 'shared/ReactSymbols';
43
44
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
45
import {
@@ -771,8 +774,8 @@ function use<T>(usable: Usable<T>): T {
774
}
775
}
776
} else if (
774
- usable.$$typeof != null &&
775
- usable.$$typeof === REACT_CONTEXT_TYPE
777
+ usable.$$typeof === REACT_CONTEXT_TYPE ||
778
+ usable.$$typeof === REACT_SERVER_CONTEXT_TYPE
779
) {
780
const context: ReactContext<T> = (usable: any);
781
return readContext(context);
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
+34
@@ -594,6 +594,40 @@ describe('ReactFlightDOMBrowser', () => {
594
expect(container.innerHTML).toBe('ABC');
595
});
596
597
+ // @gate enableUseHook
598
+ it('basic use(context)', async () => {
599
+ const ContextA = React.createServerContext('ContextA', '');
600
+ const ContextB = React.createServerContext('ContextB', 'B');
601
+
602
+ function ServerComponent() {
603
+ return use(ContextA) + use(ContextB);
604
+ }
605
+ function Server() {
606
+ return (
607
+ <ContextA.Provider value="A">
608
+ <ServerComponent />
609
+ </ContextA.Provider>
610
+ );
611
+ }
612
+ const stream = ReactServerDOMWriter.renderToReadableStream(<Server />);
613
+ const response = ReactServerDOMReader.createFromReadableStream(stream);
614
+
615
+ function Client() {
616
+ return response.readRoot();
617
+ }
618
+
619
+ const container = document.createElement('div');
620
+ const root = ReactDOMClient.createRoot(container);
621
+ await act(async () => {
622
+ // Client uses a different renderer.
623
+ // We reset _currentRenderer here to not trigger a warning about multiple
624
+ // renderers concurrently using this context
625
+ ContextA._currentRenderer = null;
626
+ root.render(<Client />);
627
+ });
628
+ expect(container.innerHTML).toBe('AB');
629
+ });
630
+
631
// @gate enableUseHook
632
it('use(promise) in multiple components', async () => {
633
function Child({prefix}) {
packages/react-server/src/ReactFizzHooks.js
+10
-2
@@ -39,6 +39,10 @@ import {
39
enableUseMemoCacheHook,
40
} from 'shared/ReactFeatureFlags';
41
import is from 'shared/objectIs';
42
+import {
43
+ REACT_SERVER_CONTEXT_TYPE,
44
+ REACT_CONTEXT_TYPE,
45
+} from 'shared/ReactSymbols';
46
47
type BasicStateAction<S> = (S => S) | S;
48
type Dispatch<A> = A => void;
@@ -616,8 +620,12 @@ function use<T>(usable: Usable<T>): T {
620
}
621
}
622
}
619
- } else {
620
- // TODO: Add support for Context
623
+ } else if (
624
+ usable.$$typeof === REACT_CONTEXT_TYPE ||
625
+ usable.$$typeof === REACT_SERVER_CONTEXT_TYPE
626
+ ) {
627
+ const context: ReactContext<T> = (usable: any);
628
+ return readContext(context);
629
}
630
}
631
packages/react-server/src/ReactFlightHooks.js
+3
-2
@@ -200,8 +200,9 @@ function use<T>(usable: Usable<T>): T {
200
}
201
}
202
}
203
- } else {
204
- // TODO: Add support for Context
203
+ } else if (usable.$$typeof === REACT_SERVER_CONTEXT_TYPE) {
204
+ const context: ReactServerContext<T> = (usable: any);
205
+ return readContext(context);
206
}
207
}
208
packages/shared/ReactTypes.js
-1
@@ -213,5 +213,4 @@ export type StartTransitionOptions = {
213
name?: string,
214
};
215
216
-// TODO: Add Context support
216
export type Usable<T> = Thenable<T> | ReactContext<T>;