@samitouri / QOS-React-2 / commits / 1ebedbec2b

Add Server Context deprecation warning (#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 <josh.c.story@gmail.com>

Sebastian Markbåge committed Sep 28, 2023 at 11:03 UTC 1ebedbec2bec08e07c286ea6c3cff62737a0fd3a
5 files changed +118 -36
packages/react-client/src/__tests__/ReactFlight-test.js
+23 -25
@@ -98,6 +98,19 @@ describe('ReactFlight', () => {
98 jest.restoreAllMocks();
99 });
100
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 +
114 function clientReference(value) {
115 return Object.defineProperties(
116 function () {
@@ -1063,7 +1076,7 @@ describe('ReactFlight', () => {
1076 describe('ServerContext', () => {
1077 // @gate enableServerContext
1078 it('supports basic createServerContext usage', async () => {
1066 - const ServerContext = React.createServerContext(
1079 + const ServerContext = createServerContext(
1080 'ServerContext',
1081 'hello from server',
1082 );
@@ -1084,10 +1097,7 @@ describe('ReactFlight', () => {
1097
1098 // @gate enableServerContext
1099 it('propagates ServerContext providers in flight', async () => {
1087 - const ServerContext = React.createServerContext(
1088 - 'ServerContext',
1089 - 'default',
1090 - );
1100 + const ServerContext = createServerContext('ServerContext', 'default');
1101
1102 function Foo() {
1103 return (
@@ -1115,7 +1125,7 @@ describe('ReactFlight', () => {
1125
1126 // @gate enableServerContext
1127 it('errors if you try passing JSX through ServerContext value', () => {
1118 - const ServerContext = React.createServerContext('ServerContext', {
1128 + const ServerContext = createServerContext('ServerContext', {
1129 foo: {
1130 bar: <span>hi this is default</span>,
1131 },
@@ -1149,10 +1159,7 @@ describe('ReactFlight', () => {
1159
1160 // @gate enableServerContext
1161 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');
1163
1164 function Foo() {
1165 return (
@@ -1196,10 +1203,7 @@ describe('ReactFlight', () => {
1203
1204 // @gate enableServerContext
1205 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');
1207
1208 function Foo() {
1209 return (
@@ -1254,10 +1258,7 @@ describe('ReactFlight', () => {
1258
1259 // @gate enableServerContext
1260 it('serializes ServerContext to client', async () => {
1257 - const ServerContext = React.createServerContext(
1258 - 'ServerContext',
1259 - 'default',
1260 - );
1261 + const ServerContext = createServerContext('ServerContext', 'default');
1262
1263 function ClientBar() {
1264 Scheduler.log('ClientBar');
@@ -1294,16 +1295,13 @@ describe('ReactFlight', () => {
1295 expect(ReactNoop).toMatchRenderedOutput(<span>hi this is server</span>);
1296
1297 expect(() => {
1297 - React.createServerContext('ServerContext', 'default');
1298 + createServerContext('ServerContext', 'default');
1299 }).toThrow('ServerContext: ServerContext already defined');
1300 });
1301
1302 // @gate enableServerContext
1303 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');
1305 function Bar() {
1306 return <span>{React.useContext(ServerContext)}</span>;
1307 }
@@ -1323,7 +1321,7 @@ describe('ReactFlight', () => {
1321 let ServerContext;
1322 function inlineLazyServerContextInitialization() {
1323 if (!ServerContext) {
1326 - ServerContext = React.createServerContext('ServerContext', 'default');
1324 + ServerContext = createServerContext('ServerContext', 'default');
1325 }
1326 return ServerContext;
1327 }
@@ -1331,7 +1329,7 @@ describe('ReactFlight', () => {
1329 let ClientContext;
1330 function inlineContextInitialization() {
1331 if (!ClientContext) {
1334 - ClientContext = React.createServerContext('ServerContext', 'default');
1332 + ClientContext = createServerContext('ServerContext', 'default', true);
1333 }
1334 return ClientContext;
1335 }
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+17 -2
@@ -3321,12 +3321,19 @@ describe('ReactDOMFizzServer', () => {
3321 let ServerContext;
3322 function inlineLazyServerContextInitialization() {
3323 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 + );
3331 }
3332 return ServerContext;
3333 }
3334
3335 function Foo() {
3336 + React.useState(); // component stack generation shouldn't reinit
3337 inlineLazyServerContextInitialization();
3338 return (
3339 <>
@@ -5604,7 +5611,15 @@ describe('ReactDOMFizzServer', () => {
5611 it('basic use(context)', async () => {
5612 const ContextA = React.createContext('default');
5613 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 + );
5623 function Client() {
5624 return use(ContextA) + use(ContextB);
5625 }
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
+16 -2
@@ -609,8 +609,22 @@ describe('ReactFlightDOMBrowser', () => {
609 });
610
611 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 + );
628
629 function ServerComponent() {
630 return use(ContextA) + use(ContextB);
packages/react/src/ReactServerContext.js
+7
@@ -30,6 +30,13 @@ export function createServerContext<T: ServerContextJSONValue>(
30 if (!enableServerContext) {
31 throw new Error('Not implemented.');
32 }
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 + }
40 let wasDefined = true;
41 if (!ContextRegistry[globalName]) {
42 wasDefined = false;
packages/shared/ReactServerContextRegistry.js
+55 -7
@@ -9,9 +9,13 @@
9
10 import type {ReactServerContext} from 'shared/ReactTypes';
11
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 +
18 import ReactSharedInternals from 'shared/ReactSharedInternals';
14 -import {createServerContext} from 'react';
19
20 const ContextRegistry = ReactSharedInternals.ContextRegistry;
21
@@ -19,11 +23,55 @@ export function getOrCreateServerContext(
23 globalName: string,
24 ): ReactServerContext<any> {
25 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;
75 }
76 return ContextRegistry[globalName];
77 }