diff --git a/fixtures/flight/src/App.js b/fixtures/flight/src/App.js
index 0bfe0fe630..9f8deed495 100644
--- a/fixtures/flight/src/App.js
+++ b/fixtures/flight/src/App.js
@@ -2,7 +2,7 @@ import * as React from 'react';
import {renderToReadableStream} from 'react-server-dom-unbundled/server';
import {createFromReadableStream} from 'react-server-dom-webpack/client';
import {PassThrough, Readable} from 'stream';
-
+import {ClientContext, ClientReadContext} from './ClientContext.js';
import Container from './Container.js';
import {Counter} from './Counter.js';
@@ -235,6 +235,11 @@ export default async function App({prerender, noCache}) {
{dedupedChild}
{Promise.resolve([dedupedChild])}
+
+
+
+
+
{prerender ? null : ( // TODO: prerender is broken for large content for some reason.
diff --git a/fixtures/flight/src/ClientContext.js b/fixtures/flight/src/ClientContext.js
new file mode 100644
index 0000000000..7d9340e62a
--- /dev/null
+++ b/fixtures/flight/src/ClientContext.js
@@ -0,0 +1,12 @@
+'use client';
+
+import {createContext, use} from 'react';
+
+const ClientContext = createContext(null);
+
+function ClientReadContext() {
+ const value = use(ClientContext);
+ return {value}
;
+}
+
+export {ClientContext, ClientReadContext};
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index 4ea1a15263..48ced58873 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -128,6 +128,7 @@ import {
REACT_LAZY_TYPE,
REACT_FORWARD_REF_TYPE,
REACT_MEMO_TYPE,
+ REACT_CONTEXT_TYPE,
} from 'shared/ReactSymbols';
import {setCurrentFiber} from './ReactCurrentFiber';
import {
@@ -2140,6 +2141,10 @@ function mountLazyComponent(
props,
renderLanes,
);
+ } else if ($$typeof === REACT_CONTEXT_TYPE) {
+ workInProgress.tag = ContextProvider;
+ workInProgress.type = Component;
+ return updateContextProvider(null, workInProgress, renderLanes);
}
}
diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
index 54e6c1aabf..0cb58d84a7 100644
--- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
@@ -116,6 +116,44 @@ describe('ReactLazy', () => {
expect(root).toMatchRenderedOutput('Hi again');
});
+ it('renders a lazy context provider', async () => {
+ const Context = React.createContext('default');
+ function ConsumerText() {
+ return ;
+ }
+ // Context.Provider === Context, so we can lazy-load the context itself
+ const LazyProvider = lazy(() => fakeImport(Context));
+
+ const root = ReactTestRenderer.create(
+ }>
+
+
+
+ ,
+ {
+ unstable_isConcurrent: true,
+ },
+ );
+
+ await waitForAll(['Loading...']);
+ expect(root).not.toMatchRenderedOutput('Hi');
+
+ await act(() => resolveFakeImport(Context));
+ assertLog(['Hi']);
+ expect(root).toMatchRenderedOutput('Hi');
+
+ // Should not suspend on update
+ root.update(
+ }>
+
+
+
+ ,
+ );
+ await waitForAll(['Hi again']);
+ expect(root).toMatchRenderedOutput('Hi again');
+ });
+
it('can resolve synchronously without suspending', async () => {
const LazyText = lazy(() => ({
then(cb) {
@@ -858,13 +896,20 @@ describe('ReactLazy', () => {
);
});
- it('throws with a useful error when wrapping Context with lazy()', async () => {
- const Context = React.createContext(null);
- const BadLazy = lazy(() => fakeImport(Context));
+ it('renders a lazy context provider without value prop', async () => {
+ // Context providers work when wrapped in lazy()
+ const Context = React.createContext('default');
+ const LazyProvider = lazy(() => fakeImport(Context));
+
+ function ConsumerText() {
+ return ;
+ }
const root = ReactTestRenderer.create(
}>
-
+
+
+
,
{
unstable_isConcurrent: true,
@@ -873,16 +918,9 @@ describe('ReactLazy', () => {
await waitForAll(['Loading...']);
- await resolveFakeImport(Context);
- root.update(
- }>
-
- ,
- );
- await waitForThrow(
- 'Element type is invalid. Received a promise that resolves to: Context. ' +
- 'Lazy element type must resolve to a class or function.',
- );
+ await act(() => resolveFakeImport(Context));
+ assertLog(['provided']);
+ expect(root).toMatchRenderedOutput('provided');
});
it('throws with a useful error when wrapping Context.Consumer with lazy()', async () => {
diff --git a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js
index 082a9c0ce5..50f83d31d8 100644
--- a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js
+++ b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js
@@ -182,11 +182,10 @@ const deepProxyHandlers: Proxy$traps = {
// $FlowFixMe[prop-missing]
return Object.prototype[Symbol.toStringTag];
case 'Provider':
- throw new Error(
- `Cannot render a Client Context Provider on the Server. ` +
- `Instead, you can export a Client Component wrapper ` +
- `that itself renders a Client Context Provider.`,
- );
+ // Context.Provider === Context in React, so return the same reference.
+ // This allows server components to render
+ // which will be serialized and executed on the client.
+ return receiver;
case 'then':
throw new Error(
`Cannot await or return from a thenable. ` +
diff --git a/packages/react-server-dom-unbundled/src/ReactFlightUnbundledReferences.js b/packages/react-server-dom-unbundled/src/ReactFlightUnbundledReferences.js
index 07646e18ec..b9f90b4d14 100644
--- a/packages/react-server-dom-unbundled/src/ReactFlightUnbundledReferences.js
+++ b/packages/react-server-dom-unbundled/src/ReactFlightUnbundledReferences.js
@@ -182,11 +182,10 @@ const deepProxyHandlers: Proxy$traps = {
// $FlowFixMe[prop-missing]
return Object.prototype[Symbol.toStringTag];
case 'Provider':
- throw new Error(
- `Cannot render a Client Context Provider on the Server. ` +
- `Instead, you can export a Client Component wrapper ` +
- `that itself renders a Client Context Provider.`,
- );
+ // Context.Provider === Context in React, so return the same reference.
+ // This allows server components to render
+ // which will be serialized and executed on the client.
+ return receiver;
case 'then':
throw new Error(
`Cannot await or return from a thenable. ` +
diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js
index 07646e18ec..b9f90b4d14 100644
--- a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js
+++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js
@@ -182,11 +182,10 @@ const deepProxyHandlers: Proxy$traps = {
// $FlowFixMe[prop-missing]
return Object.prototype[Symbol.toStringTag];
case 'Provider':
- throw new Error(
- `Cannot render a Client Context Provider on the Server. ` +
- `Instead, you can export a Client Component wrapper ` +
- `that itself renders a Client Context Provider.`,
- );
+ // Context.Provider === Context in React, so return the same reference.
+ // This allows server components to render
+ // which will be serialized and executed on the client.
+ return receiver;
case 'then':
throw new Error(
`Cannot await or return from a thenable. ` +
diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
index 728b8ac197..7070f39cea 100644
--- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
@@ -787,7 +787,7 @@ describe('ReactFlightDOM', () => {
;
});
- it('throws when accessing a Context.Provider below the client exports', () => {
+ it('does not throw when accessing a Context.Provider from client exports', () => {
const Context = React.createContext();
const ClientModule = clientExports({
Context,
@@ -795,11 +795,60 @@ describe('ReactFlightDOM', () => {
function dotting() {
return ClientModule.Context.Provider;
}
- expect(dotting).toThrowError(
- `Cannot render a Client Context Provider on the Server. ` +
- `Instead, you can export a Client Component wrapper ` +
- `that itself renders a Client Context Provider.`,
+ expect(dotting).not.toThrowError();
+ });
+
+ it('can render a client Context.Provider from a server component', async () => {
+ // Create a context in a client module
+ const TestContext = React.createContext('default');
+ const ClientModule = clientExports({
+ TestContext,
+ });
+
+ // Client component that reads context
+ function ClientConsumer() {
+ const value = React.useContext(TestContext);
+ return {value};
+ }
+ const {ClientConsumer: ClientConsumerRef} = clientExports({ClientConsumer});
+
+ function Print({response}) {
+ return use(response);
+ }
+
+ function App({response}) {
+ return (
+ Loading...}>
+
+
+ );
+ }
+
+ // Server component that provides context
+ function ServerApp() {
+ return (
+
+
+
+
+
+ );
+ }
+
+ const {writable, readable} = getTestStream();
+ const {pipe} = await serverAct(() =>
+ ReactServerDOMServer.renderToPipeableStream(, webpackMap),
);
+ pipe(writable);
+ const response = ReactServerDOMClient.createFromReadableStream(readable);
+
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render();
+ });
+
+ expect(container.innerHTML).toBe('from-server
');
});
it('should progressively reveal server components', async () => {
diff --git a/packages/react-server/src/ReactFlightServerTemporaryReferences.js b/packages/react-server/src/ReactFlightServerTemporaryReferences.js
index 581fdc70fe..9195afa494 100644
--- a/packages/react-server/src/ReactFlightServerTemporaryReferences.js
+++ b/packages/react-server/src/ReactFlightServerTemporaryReferences.js
@@ -65,11 +65,10 @@ const proxyHandlers: Proxy$traps = {
// $FlowFixMe[prop-missing]
return Object.prototype[Symbol.toStringTag];
case 'Provider':
- throw new Error(
- `Cannot render a Client Context Provider on the Server. ` +
- `Instead, you can export a Client Component wrapper ` +
- `that itself renders a Client Context Provider.`,
- );
+ // Context.Provider === Context in React, so return the same reference.
+ // This allows server components to render
+ // which will be serialized and executed on the client.
+ return receiver;
case 'then':
// Allow returning a temporary reference from an async function
// Unlike regular Client References, a Promise would never have been serialized as