Skip to content

Commit 3613284

Browse files
authored
experimental_use(context) for server components and ssr (#25226)
implements the experimental use(context) API for the server components (Flight) and SSR (Fizz) runtimes
1 parent d5ddc65 commit 3613284

File tree

7 files changed

+107
-11
lines changed

7 files changed

+107
-11
lines changed

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

+48
Original file line numberDiff line numberDiff line change
@@ -5292,6 +5292,54 @@ describe('ReactDOMFizzServer', () => {
52925292
expect(getVisibleChildren(container)).toEqual('ABC');
52935293
});
52945294

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+
52955343
// @gate enableUseHook
52965344
it('use(promise) in multiple components', async () => {
52975345
const promiseA = Promise.resolve('A');

packages/react-reconciler/src/ReactFiberHooks.new.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ import {
3636
enableUseHook,
3737
enableUseMemoCacheHook,
3838
} 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';
4043

4144
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
4245
import {
@@ -771,8 +774,8 @@ function use<T>(usable: Usable<T>): T {
771774
}
772775
}
773776
} 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
776779
) {
777780
const context: ReactContext<T> = (usable: any);
778781
return readContext(context);

packages/react-reconciler/src/ReactFiberHooks.old.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ import {
3636
enableUseHook,
3737
enableUseMemoCacheHook,
3838
} 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';
4043

4144
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
4245
import {
@@ -771,8 +774,8 @@ function use<T>(usable: Usable<T>): T {
771774
}
772775
}
773776
} 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
776779
) {
777780
const context: ReactContext<T> = (usable: any);
778781
return readContext(context);

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js

+34
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,40 @@ describe('ReactFlightDOMBrowser', () => {
594594
expect(container.innerHTML).toBe('ABC');
595595
});
596596

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+
597631
// @gate enableUseHook
598632
it('use(promise) in multiple components', async () => {
599633
function Child({prefix}) {

packages/react-server/src/ReactFizzHooks.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ import {
3939
enableUseMemoCacheHook,
4040
} from 'shared/ReactFeatureFlags';
4141
import is from 'shared/objectIs';
42+
import {
43+
REACT_SERVER_CONTEXT_TYPE,
44+
REACT_CONTEXT_TYPE,
45+
} from 'shared/ReactSymbols';
4246

4347
type BasicStateAction<S> = (S => S) | S;
4448
type Dispatch<A> = A => void;
@@ -616,8 +620,12 @@ function use<T>(usable: Usable<T>): T {
616620
}
617621
}
618622
}
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);
621629
}
622630
}
623631

packages/react-server/src/ReactFlightHooks.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,9 @@ function use<T>(usable: Usable<T>): T {
200200
}
201201
}
202202
}
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);
205206
}
206207
}
207208

packages/shared/ReactTypes.js

-1
Original file line numberDiff line numberDiff line change
@@ -213,5 +213,4 @@ export type StartTransitionOptions = {
213213
name?: string,
214214
};
215215

216-
// TODO: Add Context support
217216
export type Usable<T> = Thenable<T> | ReactContext<T>;

0 commit comments

Comments
 (0)