Skip to content

Commit eb083b7

Browse files
mofeiZrickhanlonii
authored andcommitted
experimental_use(context)(#25202)
1 parent ce111ba commit eb083b7

File tree

4 files changed

+141
-111
lines changed

4 files changed

+141
-111
lines changed

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

+58-55
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
enableUseHook,
3737
enableUseMemoCacheHook,
3838
} from 'shared/ReactFeatureFlags';
39+
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
3940

4041
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
4142
import {
@@ -714,68 +715,70 @@ function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
714715
}
715716

716717
function use<T>(usable: Usable<T>): T {
717-
if (
718-
usable !== null &&
719-
typeof usable === 'object' &&
720-
typeof usable.then === 'function'
721-
) {
722-
// This is a thenable.
723-
const thenable: Thenable<T> = (usable: any);
724-
725-
// Track the position of the thenable within this fiber.
726-
const index = thenableIndexCounter;
727-
thenableIndexCounter += 1;
728-
729-
switch (thenable.status) {
730-
case 'fulfilled': {
731-
const fulfilledValue: T = thenable.value;
732-
return fulfilledValue;
733-
}
734-
case 'rejected': {
735-
const rejectedError = thenable.reason;
736-
throw rejectedError;
737-
}
738-
default: {
739-
const prevThenableAtIndex: Thenable<T> | null = getPreviouslyUsedThenableAtIndex(
740-
index,
741-
);
742-
if (prevThenableAtIndex !== null) {
743-
switch (prevThenableAtIndex.status) {
744-
case 'fulfilled': {
745-
const fulfilledValue: T = prevThenableAtIndex.value;
746-
return fulfilledValue;
747-
}
748-
case 'rejected': {
749-
const rejectedError: mixed = prevThenableAtIndex.reason;
750-
throw rejectedError;
751-
}
752-
default: {
753-
// The thenable still hasn't resolved. Suspend with the same
754-
// thenable as last time to avoid redundant listeners.
755-
throw prevThenableAtIndex;
718+
if (usable !== null && typeof usable === 'object') {
719+
if (typeof usable.then === 'function') {
720+
// This is a thenable.
721+
const thenable: Thenable<T> = (usable: any);
722+
723+
// Track the position of the thenable within this fiber.
724+
const index = thenableIndexCounter;
725+
thenableIndexCounter += 1;
726+
727+
switch (thenable.status) {
728+
case 'fulfilled': {
729+
const fulfilledValue: T = thenable.value;
730+
return fulfilledValue;
731+
}
732+
case 'rejected': {
733+
const rejectedError = thenable.reason;
734+
throw rejectedError;
735+
}
736+
default: {
737+
const prevThenableAtIndex: Thenable<T> | null = getPreviouslyUsedThenableAtIndex(
738+
index,
739+
);
740+
if (prevThenableAtIndex !== null) {
741+
switch (prevThenableAtIndex.status) {
742+
case 'fulfilled': {
743+
const fulfilledValue: T = prevThenableAtIndex.value;
744+
return fulfilledValue;
745+
}
746+
case 'rejected': {
747+
const rejectedError: mixed = prevThenableAtIndex.reason;
748+
throw rejectedError;
749+
}
750+
default: {
751+
// The thenable still hasn't resolved. Suspend with the same
752+
// thenable as last time to avoid redundant listeners.
753+
throw prevThenableAtIndex;
754+
}
756755
}
756+
} else {
757+
// This is the first time something has been used at this index.
758+
// Stash the thenable at the current index so we can reuse it during
759+
// the next attempt.
760+
trackUsedThenable(thenable, index);
761+
762+
// Suspend.
763+
// TODO: Throwing here is an implementation detail that allows us to
764+
// unwind the call stack. But we shouldn't allow it to leak into
765+
// userspace. Throw an opaque placeholder value instead of the
766+
// actual thenable. If it doesn't get captured by the work loop, log
767+
// a warning, because that means something in userspace must have
768+
// caught it.
769+
throw thenable;
757770
}
758-
} else {
759-
// This is the first time something has been used at this index.
760-
// Stash the thenable at the current index so we can reuse it during
761-
// the next attempt.
762-
trackUsedThenable(thenable, index);
763-
764-
// Suspend.
765-
// TODO: Throwing here is an implementation detail that allows us to
766-
// unwind the call stack. But we shouldn't allow it to leak into
767-
// userspace. Throw an opaque placeholder value instead of the
768-
// actual thenable. If it doesn't get captured by the work loop, log
769-
// a warning, because that means something in userspace must have
770-
// caught it.
771-
throw thenable;
772771
}
773772
}
773+
} else if (
774+
usable.$$typeof != null &&
775+
usable.$$typeof === REACT_CONTEXT_TYPE
776+
) {
777+
const context: ReactContext<T> = (usable: any);
778+
return readContext(context);
774779
}
775780
}
776781

777-
// TODO: Add support for Context
778-
779782
// eslint-disable-next-line react-internal/safe-string-coercion
780783
throw new Error('An unsupported type was passed to use(): ' + String(usable));
781784
}

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

+58-55
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
enableUseHook,
3737
enableUseMemoCacheHook,
3838
} from 'shared/ReactFeatureFlags';
39+
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
3940

4041
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
4142
import {
@@ -714,68 +715,70 @@ function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
714715
}
715716

716717
function use<T>(usable: Usable<T>): T {
717-
if (
718-
usable !== null &&
719-
typeof usable === 'object' &&
720-
typeof usable.then === 'function'
721-
) {
722-
// This is a thenable.
723-
const thenable: Thenable<T> = (usable: any);
724-
725-
// Track the position of the thenable within this fiber.
726-
const index = thenableIndexCounter;
727-
thenableIndexCounter += 1;
728-
729-
switch (thenable.status) {
730-
case 'fulfilled': {
731-
const fulfilledValue: T = thenable.value;
732-
return fulfilledValue;
733-
}
734-
case 'rejected': {
735-
const rejectedError = thenable.reason;
736-
throw rejectedError;
737-
}
738-
default: {
739-
const prevThenableAtIndex: Thenable<T> | null = getPreviouslyUsedThenableAtIndex(
740-
index,
741-
);
742-
if (prevThenableAtIndex !== null) {
743-
switch (prevThenableAtIndex.status) {
744-
case 'fulfilled': {
745-
const fulfilledValue: T = prevThenableAtIndex.value;
746-
return fulfilledValue;
747-
}
748-
case 'rejected': {
749-
const rejectedError: mixed = prevThenableAtIndex.reason;
750-
throw rejectedError;
751-
}
752-
default: {
753-
// The thenable still hasn't resolved. Suspend with the same
754-
// thenable as last time to avoid redundant listeners.
755-
throw prevThenableAtIndex;
718+
if (usable !== null && typeof usable === 'object') {
719+
if (typeof usable.then === 'function') {
720+
// This is a thenable.
721+
const thenable: Thenable<T> = (usable: any);
722+
723+
// Track the position of the thenable within this fiber.
724+
const index = thenableIndexCounter;
725+
thenableIndexCounter += 1;
726+
727+
switch (thenable.status) {
728+
case 'fulfilled': {
729+
const fulfilledValue: T = thenable.value;
730+
return fulfilledValue;
731+
}
732+
case 'rejected': {
733+
const rejectedError = thenable.reason;
734+
throw rejectedError;
735+
}
736+
default: {
737+
const prevThenableAtIndex: Thenable<T> | null = getPreviouslyUsedThenableAtIndex(
738+
index,
739+
);
740+
if (prevThenableAtIndex !== null) {
741+
switch (prevThenableAtIndex.status) {
742+
case 'fulfilled': {
743+
const fulfilledValue: T = prevThenableAtIndex.value;
744+
return fulfilledValue;
745+
}
746+
case 'rejected': {
747+
const rejectedError: mixed = prevThenableAtIndex.reason;
748+
throw rejectedError;
749+
}
750+
default: {
751+
// The thenable still hasn't resolved. Suspend with the same
752+
// thenable as last time to avoid redundant listeners.
753+
throw prevThenableAtIndex;
754+
}
756755
}
756+
} else {
757+
// This is the first time something has been used at this index.
758+
// Stash the thenable at the current index so we can reuse it during
759+
// the next attempt.
760+
trackUsedThenable(thenable, index);
761+
762+
// Suspend.
763+
// TODO: Throwing here is an implementation detail that allows us to
764+
// unwind the call stack. But we shouldn't allow it to leak into
765+
// userspace. Throw an opaque placeholder value instead of the
766+
// actual thenable. If it doesn't get captured by the work loop, log
767+
// a warning, because that means something in userspace must have
768+
// caught it.
769+
throw thenable;
757770
}
758-
} else {
759-
// This is the first time something has been used at this index.
760-
// Stash the thenable at the current index so we can reuse it during
761-
// the next attempt.
762-
trackUsedThenable(thenable, index);
763-
764-
// Suspend.
765-
// TODO: Throwing here is an implementation detail that allows us to
766-
// unwind the call stack. But we shouldn't allow it to leak into
767-
// userspace. Throw an opaque placeholder value instead of the
768-
// actual thenable. If it doesn't get captured by the work loop, log
769-
// a warning, because that means something in userspace must have
770-
// caught it.
771-
throw thenable;
772771
}
773772
}
773+
} else if (
774+
usable.$$typeof != null &&
775+
usable.$$typeof === REACT_CONTEXT_TYPE
776+
) {
777+
const context: ReactContext<T> = (usable: any);
778+
return readContext(context);
774779
}
775780
}
776781

777-
// TODO: Add support for Context
778-
779782
// eslint-disable-next-line react-internal/safe-string-coercion
780783
throw new Error('An unsupported type was passed to use(): ' + String(usable));
781784
}

packages/react-reconciler/src/__tests__/ReactWakeable-test.js

+24
Original file line numberDiff line numberDiff line change
@@ -315,4 +315,28 @@ describe('ReactWakeable', () => {
315315
]);
316316
expect(root).toMatchRenderedOutput('Caught an error: Oops!');
317317
});
318+
319+
// @gate enableUseHook
320+
test('basic use(context)', () => {
321+
const ContextA = React.createContext('');
322+
const ContextB = React.createContext('B');
323+
324+
function Sync() {
325+
const text = use(ContextA) + use(ContextB);
326+
return text;
327+
}
328+
329+
function App() {
330+
return (
331+
<ContextA.Provider value="A">
332+
<Sync />
333+
</ContextA.Provider>
334+
);
335+
}
336+
337+
const root = ReactNoop.createRoot();
338+
root.render(<App />);
339+
expect(Scheduler).toFlushWithoutYielding();
340+
expect(root).toMatchRenderedOutput('AB');
341+
});
318342
});

packages/shared/ReactTypes.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,4 @@ export type StartTransitionOptions = {
214214
};
215215

216216
// TODO: Add Context support
217-
export type Usable<T> = Thenable<T>;
217+
export type Usable<T> = Thenable<T> | ReactContext<T>;

0 commit comments

Comments
 (0)