@samitouri / QOS-React-2 / commits / 3613284dce

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>;