Skip to content

Commit a392a68

Browse files
committed
Warn on readContext() in SSR inside useMemo and useReducer
1 parent b54200e commit a392a68

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,4 +717,36 @@ describe('ReactDOMServerHooks', () => {
717717
expect(domNode.textContent).toEqual('undefined');
718718
});
719719
});
720+
721+
describe('readContext', () => {
722+
function readContext(Context, observedBits) {
723+
const dispatcher =
724+
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
725+
.ReactCurrentDispatcher.current;
726+
return dispatcher.readContext(Context, observedBits);
727+
}
728+
729+
itRenders('with a warning inside useMemo and useReducer', async render => {
730+
const Context = React.createContext(42);
731+
732+
function ReadInMemo(props) {
733+
let count = React.useMemo(() => readContext(Context), []);
734+
return <Text text={count} />;
735+
}
736+
737+
function ReadInReducer(props) {
738+
let [count, dispatch] = React.useReducer(() => readContext(Context));
739+
if (count !== 42) {
740+
dispatch();
741+
}
742+
return <Text text={count} />;
743+
}
744+
745+
const domNode1 = await render(<ReadInMemo />, 1);
746+
expect(domNode1.textContent).toEqual('42');
747+
748+
const domNode2 = await render(<ReadInReducer />, 1);
749+
expect(domNode2.textContent).toEqual('42');
750+
});
751+
});
720752
});

packages/react-dom/src/server/ReactPartialRendererHooks.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ let renderPhaseUpdates: Map<UpdateQueue<any>, Update<any>> | null = null;
4949
let numberOfReRenders: number = 0;
5050
const RE_RENDER_LIMIT = 25;
5151

52+
let shouldWarnAboutReadingContextInDEV = false;
53+
5254
// In DEV, this is the name of the currently executing primitive hook
5355
let currentHookNameInDev: ?string;
5456

@@ -137,6 +139,9 @@ function createWorkInProgressHook(): Hook {
137139

138140
export function prepareToUseHooks(componentIdentity: Object): void {
139141
currentlyRenderingComponent = componentIdentity;
142+
if (__DEV__) {
143+
shouldWarnAboutReadingContextInDEV = false;
144+
}
140145

141146
// The following should have already been reset
142147
// didScheduleRenderPhaseUpdate = false;
@@ -173,6 +178,9 @@ export function finishHooks(
173178
numberOfReRenders = 0;
174179
renderPhaseUpdates = null;
175180
workInProgressHook = null;
181+
if (__DEV__) {
182+
shouldWarnAboutReadingContextInDEV = false;
183+
}
176184

177185
// These were reset above
178186
// currentlyRenderingComponent = null;
@@ -191,6 +199,15 @@ function readContext<T>(
191199
): T {
192200
let threadID = currentThreadID;
193201
validateContextBounds(context, threadID);
202+
if (__DEV__) {
203+
warning(
204+
!shouldWarnAboutReadingContextInDEV,
205+
'Context can only be read while React is rendering. ' +
206+
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
207+
'In function components, you can read it directly in the function body, but not ' +
208+
'inside Hooks like useReducer() or useMemo().',
209+
);
210+
}
194211
return context[threadID];
195212
}
196213

@@ -255,7 +272,13 @@ export function useReducer<S, A>(
255272
const action = update.action;
256273
// Temporarily clear to forbid calling Hooks.
257274
currentlyRenderingComponent = null;
275+
if (__DEV__) {
276+
shouldWarnAboutReadingContextInDEV = true;
277+
}
258278
newState = reducer(newState, action);
279+
if (__DEV__) {
280+
shouldWarnAboutReadingContextInDEV = false;
281+
}
259282
currentlyRenderingComponent = component;
260283
update = update.next;
261284
} while (update !== null);
@@ -311,8 +334,14 @@ function useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T {
311334

312335
// Temporarily clear to forbid calling Hooks.
313336
currentlyRenderingComponent = null;
337+
if (__DEV__) {
338+
shouldWarnAboutReadingContextInDEV = true;
339+
}
314340
const nextValue = nextCreate();
315341
currentlyRenderingComponent = component;
342+
if (__DEV__) {
343+
shouldWarnAboutReadingContextInDEV = false;
344+
}
316345
workInProgressHook.memoizedState = [nextValue, nextDeps];
317346
return nextValue;
318347
}

0 commit comments

Comments
 (0)