diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 243c7dba8607c..4d8e677e03739 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -5292,6 +5292,54 @@ describe('ReactDOMFizzServer', () => { expect(getVisibleChildren(container)).toEqual('ABC'); }); + // @gate enableUseHook + it('basic use(context)', async () => { + const ContextA = React.createContext('default'); + const ContextB = React.createContext('B'); + const ServerContext = React.createServerContext( + 'ServerContext', + 'default', + ); + function Client() { + return use(ContextA) + use(ContextB); + } + function ServerComponent() { + return use(ServerContext); + } + function Server() { + return ( + + + + ); + } + function App() { + return ( + <> + + + + + + ); + } + + await act(async () => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(); + pipe(writable); + }); + expect(getVisibleChildren(container)).toEqual(['AB', 'C']); + + // Hydration uses a different renderer runtime (Fiber instead of Fizz). + // We reset _currentRenderer here to not trigger a warning about multiple + // renderers concurrently using these contexts + ContextA._currentRenderer = null; + ServerContext._currentRenderer = null; + ReactDOMClient.hydrateRoot(container, ); + expect(Scheduler).toFlushAndYield([]); + expect(getVisibleChildren(container)).toEqual(['AB', 'C']); + }); + // @gate enableUseHook it('use(promise) in multiple components', async () => { const promiseA = Promise.resolve('A'); diff --git a/packages/react-reconciler/src/ReactFiberHooks.new.js b/packages/react-reconciler/src/ReactFiberHooks.new.js index ee3d118a4a5a7..6ca089ff2d0a8 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.new.js +++ b/packages/react-reconciler/src/ReactFiberHooks.new.js @@ -36,7 +36,10 @@ import { enableUseHook, enableUseMemoCacheHook, } from 'shared/ReactFeatureFlags'; -import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; +import { + REACT_CONTEXT_TYPE, + REACT_SERVER_CONTEXT_TYPE, +} from 'shared/ReactSymbols'; import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode'; import { @@ -771,8 +774,8 @@ function use(usable: Usable): T { } } } else if ( - usable.$$typeof != null && - usable.$$typeof === REACT_CONTEXT_TYPE + usable.$$typeof === REACT_CONTEXT_TYPE || + usable.$$typeof === REACT_SERVER_CONTEXT_TYPE ) { const context: ReactContext = (usable: any); return readContext(context); diff --git a/packages/react-reconciler/src/ReactFiberHooks.old.js b/packages/react-reconciler/src/ReactFiberHooks.old.js index aaa5ed306e42c..133dc8d5dd6eb 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.old.js +++ b/packages/react-reconciler/src/ReactFiberHooks.old.js @@ -36,7 +36,10 @@ import { enableUseHook, enableUseMemoCacheHook, } from 'shared/ReactFeatureFlags'; -import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; +import { + REACT_CONTEXT_TYPE, + REACT_SERVER_CONTEXT_TYPE, +} from 'shared/ReactSymbols'; import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode'; import { @@ -771,8 +774,8 @@ function use(usable: Usable): T { } } } else if ( - usable.$$typeof != null && - usable.$$typeof === REACT_CONTEXT_TYPE + usable.$$typeof === REACT_CONTEXT_TYPE || + usable.$$typeof === REACT_SERVER_CONTEXT_TYPE ) { const context: ReactContext = (usable: any); return readContext(context); diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js index 1b59c72ab3e48..f477bba451a6e 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -594,6 +594,40 @@ describe('ReactFlightDOMBrowser', () => { expect(container.innerHTML).toBe('ABC'); }); + // @gate enableUseHook + it('basic use(context)', async () => { + const ContextA = React.createServerContext('ContextA', ''); + const ContextB = React.createServerContext('ContextB', 'B'); + + function ServerComponent() { + return use(ContextA) + use(ContextB); + } + function Server() { + return ( + + + + ); + } + const stream = ReactServerDOMWriter.renderToReadableStream(); + const response = ReactServerDOMReader.createFromReadableStream(stream); + + function Client() { + return response.readRoot(); + } + + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + // Client uses a different renderer. + // We reset _currentRenderer here to not trigger a warning about multiple + // renderers concurrently using this context + ContextA._currentRenderer = null; + root.render(); + }); + expect(container.innerHTML).toBe('AB'); + }); + // @gate enableUseHook it('use(promise) in multiple components', async () => { function Child({prefix}) { diff --git a/packages/react-server/src/ReactFizzHooks.js b/packages/react-server/src/ReactFizzHooks.js index 02072fabd3f37..b330605260afd 100644 --- a/packages/react-server/src/ReactFizzHooks.js +++ b/packages/react-server/src/ReactFizzHooks.js @@ -39,6 +39,10 @@ import { enableUseMemoCacheHook, } from 'shared/ReactFeatureFlags'; import is from 'shared/objectIs'; +import { + REACT_SERVER_CONTEXT_TYPE, + REACT_CONTEXT_TYPE, +} from 'shared/ReactSymbols'; type BasicStateAction = (S => S) | S; type Dispatch = A => void; @@ -616,8 +620,12 @@ function use(usable: Usable): T { } } } - } else { - // TODO: Add support for Context + } else if ( + usable.$$typeof === REACT_CONTEXT_TYPE || + usable.$$typeof === REACT_SERVER_CONTEXT_TYPE + ) { + const context: ReactContext = (usable: any); + return readContext(context); } } diff --git a/packages/react-server/src/ReactFlightHooks.js b/packages/react-server/src/ReactFlightHooks.js index f4784c82e1cf1..dfb234b15ad27 100644 --- a/packages/react-server/src/ReactFlightHooks.js +++ b/packages/react-server/src/ReactFlightHooks.js @@ -200,8 +200,9 @@ function use(usable: Usable): T { } } } - } else { - // TODO: Add support for Context + } else if (usable.$$typeof === REACT_SERVER_CONTEXT_TYPE) { + const context: ReactServerContext = (usable: any); + return readContext(context); } } diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 9ea076b15f9e7..2832535c87c95 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -213,5 +213,4 @@ export type StartTransitionOptions = {| name?: string, |}; -// TODO: Add Context support export type Usable = Thenable | ReactContext;