Skip to content

Commit 9e60fcf

Browse files
sebmarkbagegnoff
authored andcommitted
Add Server Context deprecation warning (facebook#27424)
As agreed, we're removing Server Context. This was never official documented. We've found that it's not that useful in practice. Often the better options are: - Read things off the url or global scope like params or cookies. - Use the module system for global dependency injection. - Use `React.cache()` to dedupe multiple things instead of computing once and passing down. There are still legit use cases for Server Context but you have to be very careful not to pass any large data, so in generally we recommend against it anyway. Yes, prop drilling is annoying but it's not impossible for the cases this is needed. I would personally always pick it over Server Context anyway. Semantically, Server Context also blocks object deduping due to how it plays out with Server Components that can't be deduped. This is much more important feature. Since it's already in canary along with the rest of RSC, we're adding a warning for a few versions before removing completely to help migration. --------- Co-authored-by: Josh Story <[email protected]>
1 parent 09e823b commit 9e60fcf

File tree

5 files changed

+118
-36
lines changed

5 files changed

+118
-36
lines changed

packages/react-client/src/__tests__/ReactFlight-test.js

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,19 @@ describe('ReactFlight', () => {
9898
jest.restoreAllMocks();
9999
});
100100

101+
function createServerContext(globalName, defaultValue, withStack) {
102+
let ctx;
103+
expect(() => {
104+
ctx = React.createServerContext(globalName, defaultValue);
105+
}).toErrorDev(
106+
'Server Context is deprecated and will soon be removed. ' +
107+
'It was never documented and we have found it not to be useful ' +
108+
'enough to warrant the downside it imposes on all apps.',
109+
{withoutStack: !withStack},
110+
);
111+
return ctx;
112+
}
113+
101114
function clientReference(value) {
102115
return Object.defineProperties(
103116
function () {
@@ -1063,7 +1076,7 @@ describe('ReactFlight', () => {
10631076
describe('ServerContext', () => {
10641077
// @gate enableServerContext
10651078
it('supports basic createServerContext usage', async () => {
1066-
const ServerContext = React.createServerContext(
1079+
const ServerContext = createServerContext(
10671080
'ServerContext',
10681081
'hello from server',
10691082
);
@@ -1084,10 +1097,7 @@ describe('ReactFlight', () => {
10841097

10851098
// @gate enableServerContext
10861099
it('propagates ServerContext providers in flight', async () => {
1087-
const ServerContext = React.createServerContext(
1088-
'ServerContext',
1089-
'default',
1090-
);
1100+
const ServerContext = createServerContext('ServerContext', 'default');
10911101

10921102
function Foo() {
10931103
return (
@@ -1115,7 +1125,7 @@ describe('ReactFlight', () => {
11151125

11161126
// @gate enableServerContext
11171127
it('errors if you try passing JSX through ServerContext value', () => {
1118-
const ServerContext = React.createServerContext('ServerContext', {
1128+
const ServerContext = createServerContext('ServerContext', {
11191129
foo: {
11201130
bar: <span>hi this is default</span>,
11211131
},
@@ -1149,10 +1159,7 @@ describe('ReactFlight', () => {
11491159

11501160
// @gate enableServerContext
11511161
it('propagates ServerContext and cleans up the providers in flight', async () => {
1152-
const ServerContext = React.createServerContext(
1153-
'ServerContext',
1154-
'default',
1155-
);
1162+
const ServerContext = createServerContext('ServerContext', 'default');
11561163

11571164
function Foo() {
11581165
return (
@@ -1196,10 +1203,7 @@ describe('ReactFlight', () => {
11961203

11971204
// @gate enableServerContext
11981205
it('propagates ServerContext providers in flight after suspending', async () => {
1199-
const ServerContext = React.createServerContext(
1200-
'ServerContext',
1201-
'default',
1202-
);
1206+
const ServerContext = createServerContext('ServerContext', 'default');
12031207

12041208
function Foo() {
12051209
return (
@@ -1254,10 +1258,7 @@ describe('ReactFlight', () => {
12541258

12551259
// @gate enableServerContext
12561260
it('serializes ServerContext to client', async () => {
1257-
const ServerContext = React.createServerContext(
1258-
'ServerContext',
1259-
'default',
1260-
);
1261+
const ServerContext = createServerContext('ServerContext', 'default');
12611262

12621263
function ClientBar() {
12631264
Scheduler.log('ClientBar');
@@ -1294,16 +1295,13 @@ describe('ReactFlight', () => {
12941295
expect(ReactNoop).toMatchRenderedOutput(<span>hi this is server</span>);
12951296

12961297
expect(() => {
1297-
React.createServerContext('ServerContext', 'default');
1298+
createServerContext('ServerContext', 'default');
12981299
}).toThrow('ServerContext: ServerContext already defined');
12991300
});
13001301

13011302
// @gate enableServerContext
13021303
it('takes ServerContext from the client for refetching use cases', async () => {
1303-
const ServerContext = React.createServerContext(
1304-
'ServerContext',
1305-
'default',
1306-
);
1304+
const ServerContext = createServerContext('ServerContext', 'default');
13071305
function Bar() {
13081306
return <span>{React.useContext(ServerContext)}</span>;
13091307
}
@@ -1323,15 +1321,15 @@ describe('ReactFlight', () => {
13231321
let ServerContext;
13241322
function inlineLazyServerContextInitialization() {
13251323
if (!ServerContext) {
1326-
ServerContext = React.createServerContext('ServerContext', 'default');
1324+
ServerContext = createServerContext('ServerContext', 'default');
13271325
}
13281326
return ServerContext;
13291327
}
13301328

13311329
let ClientContext;
13321330
function inlineContextInitialization() {
13331331
if (!ClientContext) {
1334-
ClientContext = React.createServerContext('ServerContext', 'default');
1332+
ClientContext = createServerContext('ServerContext', 'default', true);
13351333
}
13361334
return ClientContext;
13371335
}

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3321,12 +3321,19 @@ describe('ReactDOMFizzServer', () => {
33213321
let ServerContext;
33223322
function inlineLazyServerContextInitialization() {
33233323
if (!ServerContext) {
3324-
ServerContext = React.createServerContext('ServerContext', 'default');
3324+
expect(() => {
3325+
ServerContext = React.createServerContext('ServerContext', 'default');
3326+
}).toErrorDev(
3327+
'Server Context is deprecated and will soon be removed. ' +
3328+
'It was never documented and we have found it not to be useful ' +
3329+
'enough to warrant the downside it imposes on all apps.',
3330+
);
33253331
}
33263332
return ServerContext;
33273333
}
33283334

33293335
function Foo() {
3336+
React.useState(); // component stack generation shouldn't reinit
33303337
inlineLazyServerContextInitialization();
33313338
return (
33323339
<>
@@ -5604,7 +5611,15 @@ describe('ReactDOMFizzServer', () => {
56045611
it('basic use(context)', async () => {
56055612
const ContextA = React.createContext('default');
56065613
const ContextB = React.createContext('B');
5607-
const ServerContext = React.createServerContext('ServerContext', 'default');
5614+
let ServerContext;
5615+
expect(() => {
5616+
ServerContext = React.createServerContext('ServerContext', 'default');
5617+
}).toErrorDev(
5618+
'Server Context is deprecated and will soon be removed. ' +
5619+
'It was never documented and we have found it not to be useful ' +
5620+
'enough to warrant the downside it imposes on all apps.',
5621+
{withoutStack: true},
5622+
);
56085623
function Client() {
56095624
return use(ContextA) + use(ContextB);
56105625
}

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -609,8 +609,22 @@ describe('ReactFlightDOMBrowser', () => {
609609
});
610610

611611
it('basic use(context)', async () => {
612-
const ContextA = React.createServerContext('ContextA', '');
613-
const ContextB = React.createServerContext('ContextB', 'B');
612+
let ContextA;
613+
let ContextB;
614+
expect(() => {
615+
ContextA = React.createServerContext('ContextA', '');
616+
ContextB = React.createServerContext('ContextB', 'B');
617+
}).toErrorDev(
618+
[
619+
'Server Context is deprecated and will soon be removed. ' +
620+
'It was never documented and we have found it not to be useful ' +
621+
'enough to warrant the downside it imposes on all apps.',
622+
'Server Context is deprecated and will soon be removed. ' +
623+
'It was never documented and we have found it not to be useful ' +
624+
'enough to warrant the downside it imposes on all apps.',
625+
],
626+
{withoutStack: true},
627+
);
614628

615629
function ServerComponent() {
616630
return use(ContextA) + use(ContextB);

packages/react/src/ReactServerContext.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ export function createServerContext<T: ServerContextJSONValue>(
3030
if (!enableServerContext) {
3131
throw new Error('Not implemented.');
3232
}
33+
if (__DEV__) {
34+
console.error(
35+
'Server Context is deprecated and will soon be removed. ' +
36+
'It was never documented and we have found it not to be useful ' +
37+
'enough to warrant the downside it imposes on all apps.',
38+
);
39+
}
3340
let wasDefined = true;
3441
if (!ContextRegistry[globalName]) {
3542
wasDefined = false;

packages/shared/ReactServerContextRegistry.js

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,69 @@
99

1010
import type {ReactServerContext} from 'shared/ReactTypes';
1111

12-
import {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
12+
import {
13+
REACT_PROVIDER_TYPE,
14+
REACT_SERVER_CONTEXT_TYPE,
15+
REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED,
16+
} from 'shared/ReactSymbols';
17+
1318
import ReactSharedInternals from 'shared/ReactSharedInternals';
14-
import {createServerContext} from 'react';
1519

1620
const ContextRegistry = ReactSharedInternals.ContextRegistry;
1721

1822
export function getOrCreateServerContext(
1923
globalName: string,
2024
): ReactServerContext<any> {
2125
if (!ContextRegistry[globalName]) {
22-
ContextRegistry[globalName] = createServerContext(
23-
globalName,
24-
// $FlowFixMe[incompatible-call] function signature doesn't reflect the symbol value
25-
REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED,
26-
);
26+
const context: ReactServerContext<any> = {
27+
$$typeof: REACT_SERVER_CONTEXT_TYPE,
28+
29+
// As a workaround to support multiple concurrent renderers, we categorize
30+
// some renderers as primary and others as secondary. We only expect
31+
// there to be two concurrent renderers at most: React Native (primary) and
32+
// Fabric (secondary); React DOM (primary) and React ART (secondary).
33+
// Secondary renderers store their context values on separate fields.
34+
_currentValue: REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED,
35+
_currentValue2: REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED,
36+
37+
_defaultValue: REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED,
38+
39+
// Used to track how many concurrent renderers this context currently
40+
// supports within in a single renderer. Such as parallel server rendering.
41+
_threadCount: 0,
42+
// These are circular
43+
Provider: (null: any),
44+
Consumer: (null: any),
45+
_globalName: globalName,
46+
};
47+
48+
context.Provider = {
49+
$$typeof: REACT_PROVIDER_TYPE,
50+
_context: context,
51+
};
52+
53+
if (__DEV__) {
54+
let hasWarnedAboutUsingConsumer;
55+
context._currentRenderer = null;
56+
context._currentRenderer2 = null;
57+
Object.defineProperties(
58+
context,
59+
({
60+
Consumer: {
61+
get() {
62+
if (!hasWarnedAboutUsingConsumer) {
63+
console.error(
64+
'Consumer pattern is not supported by ReactServerContext',
65+
);
66+
hasWarnedAboutUsingConsumer = true;
67+
}
68+
return null;
69+
},
70+
},
71+
}: any),
72+
);
73+
}
74+
ContextRegistry[globalName] = context;
2775
}
2876
return ContextRegistry[globalName];
2977
}

0 commit comments

Comments
 (0)