Skip to content
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 52 additions & 16 deletions packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -568,9 +568,10 @@ function processDebugValues(
}

export function inspectHooks<Props>(
renderFunction: Props => React$Node,
renderFunction: (Props, any) => React$Node,
props: Props,
currentDispatcher: ?CurrentDispatcherRef,
legacyContext: any,
): HooksTree {
// DevTools will pass the current renderer's injected dispatcher.
// Other apps might compile debug hooks as part of their app though.
Expand All @@ -584,7 +585,11 @@ export function inspectHooks<Props>(
let ancestorStackError;
try {
ancestorStackError = new Error();
renderFunction(props);
if (legacyContext) {
renderFunction(props, legacyContext);
} else {
renderFunction(props);
}
} finally {
readHookLog = hookLog;
hookLog = [];
Expand All @@ -594,21 +599,42 @@ export function inspectHooks<Props>(
return buildTree(rootStack, readHookLog);
}

function setupContexts(contextMap: Map<ReactContext<any>, any>, fiber: Fiber) {
function setupContexts(
contextMap: Map<ReactContext<any>, any>,
fiber: Fiber,
enableLegacyContext: ?boolean,
) {
let current = fiber;
while (current) {
if (current.tag === ContextProvider) {
const providerType: ReactProviderType<any> = current.type;
const context: ReactContext<any> = providerType._context;
if (!contextMap.has(context)) {
// Store the current value that we're going to restore later.
contextMap.set(context, context._currentValue);
// Set the inner most provider value on the context.
context._currentValue = current.memoizedProps.value;
let legacyContext = null;
if (enableLegacyContext && current.elementType.contextTypes) {
let childContextFound = false;
while (current !== null && childContextFound === false) {
if (
current.stateNode &&
current.stateNode.__reactInternalMemoizedMergedChildContext
) {
childContextFound = true;
legacyContext =
current.stateNode.__reactInternalMemoizedMergedChildContext;
}
current = current.return;
}
} else {
while (current) {
if (current.tag === ContextProvider) {
const providerType: ReactProviderType<any> = current.type;
const context: ReactContext<any> = providerType._context;
if (!contextMap.has(context)) {
// Store the current value that we're going to restore later.
contextMap.set(context, context._currentValue);
// Set the inner most provider value on the context.
context._currentValue = current.memoizedProps.value;
}
}
current = current.return;
}
current = current.return;
}
return legacyContext;
}

function restoreContexts(contextMap: Map<ReactContext<any>, any>) {
Expand Down Expand Up @@ -655,6 +681,7 @@ function resolveDefaultProps(Component, baseProps) {
export function inspectHooksOfFiber(
fiber: Fiber,
currentDispatcher: ?CurrentDispatcherRef,
enableLegacyContext: ?boolean,
) {
// DevTools will pass the current renderer's injected dispatcher.
// Other apps might compile debug hooks as part of their app though.
Expand Down Expand Up @@ -686,16 +713,25 @@ export function inspectHooksOfFiber(
currentHook = (fiber.memoizedState: Hook);
const contextMap = new Map();
try {
setupContexts(contextMap, fiber);
if (fiber.tag === ForwardRef) {
if (fiber.tag === FunctionComponent) {
const legacyContext = setupContexts(
contextMap,
fiber,
enableLegacyContext,
);
return inspectHooks(type, props, currentDispatcher, legacyContext);
} else if (fiber.tag === ForwardRef) {
setupContexts(contextMap, fiber);
return inspectHooksOfForwardRef(
type.render,
props,
fiber.ref,
currentDispatcher,
);
} else {
setupContexts(contextMap, fiber);
return inspectHooks(type, props, currentDispatcher);
}
return inspectHooks(type, props, currentDispatcher);
} finally {
currentHook = null;
restoreContexts(contextMap);
Expand Down
36 changes: 36 additions & 0 deletions packages/react-devtools-shared/src/backend/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,12 @@ type ReactTypeOfSideEffectType = {|
Placement: number,
|};

type VersionFlags = {|
enableLegacyContext: boolean,
|};

// Some environments (e.g. React Native / Hermes) don't support the performance API yet.

const getCurrentTime =
typeof performance === 'object' && typeof performance.now === 'function'
? () => performance.now()
Expand All @@ -125,6 +130,7 @@ export function getInternalReactConstants(
ReactPriorityLevels: ReactPriorityLevelsType,
ReactTypeOfSideEffect: ReactTypeOfSideEffectType,
ReactTypeOfWork: WorkTagMap,
versionFlags: VersionFlags,
|} {
const ReactTypeOfSideEffect: ReactTypeOfSideEffectType = {
NoEffect: 0b00,
Expand Down Expand Up @@ -271,6 +277,14 @@ export function getInternalReactConstants(
// End of copied code.
// **********************************************************

const versionFlags: VersionFlags = {
enableLegacyContext: true,
};

if (gte(version, '17.0.0')) {
versionFlags.enableLegacyContext = false;
}

function getTypeSymbol(type: any): Symbol | number {
const symbolOrNumber =
typeof type === 'object' && type !== null ? type.$$typeof : type;
Expand Down Expand Up @@ -400,6 +414,7 @@ export function getInternalReactConstants(
ReactPriorityLevels,
ReactTypeOfWork,
ReactTypeOfSideEffect,
versionFlags,
};
}

Expand All @@ -415,6 +430,7 @@ export function attach(
ReactPriorityLevels,
ReactTypeOfWork,
ReactTypeOfSideEffect,
versionFlags,
} = getInternalReactConstants(renderer.version);
const {NoEffect, PerformedWork, Placement} = ReactTypeOfSideEffect;
const {
Expand Down Expand Up @@ -2250,6 +2266,25 @@ export function attach(
if (!shouldHideContext) {
context = stateNode.context;
}
} else if (
tag === FunctionComponent &&
versionFlags.enableLegacyContext
) {
// Try to extract legacyContext from stateless components
// which do not have stateNode
let current = fiber.return;
let childContextFound = false;
while (current !== null && childContextFound === false) {
if (
current.stateNode &&
current.stateNode.__reactInternalMemoizedMergedChildContext
) {
childContextFound = true;
context =
current.stateNode.__reactInternalMemoizedMergedChildContext;
}
current = current.return;
}
}
} else if (
typeSymbol === CONTEXT_NUMBER ||
Expand Down Expand Up @@ -2330,6 +2365,7 @@ export function attach(
hooks = inspectHooksOfFiber(
fiber,
(renderer.currentDispatcherRef: any),
versionFlags.enableLegacyContext,
);
} finally {
// Restore original console functionality.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ class LegacyContextConsumer extends Component<any> {
}
}

function FunctionalLegacyContextConsumer(props, context) {
return null;
}

FunctionalLegacyContextConsumer.contextTypes = {
null: PropTypes.any,
};

const ModernContext = createContext();
ModernContext.displayName = 'ModernContext';
const ArrayContext = createContext(contextData.array);
Expand Down Expand Up @@ -92,22 +100,33 @@ class ModernContextType extends Component<any> {
}
}

function FunctionalContextConsumer() {
function FunctionalModernContextConsumer() {
useContext(StringContext);
return null;
}

function FunctionalLegacyAndModernContextConsumer(props, context) {
useContext(StringContext);
return null;
}

FunctionalLegacyAndModernContextConsumer.contextTypes = {
null: PropTypes.any,
};

export default function Contexts() {
return (
<Fragment>
<LegacyContextProvider>
<LegacyContextConsumer />
<FunctionalLegacyContextConsumer />
<FunctionalLegacyAndModernContextConsumer />
</LegacyContextProvider>
<ModernContext.Provider value={contextData}>
<ModernContext.Consumer>{value => null}</ModernContext.Consumer>
<ModernContextType />
</ModernContext.Provider>
<FunctionalContextConsumer />
<FunctionalModernContextConsumer />
<ArrayContext.Consumer>{value => null}</ArrayContext.Consumer>
<BoolContext.Consumer>{value => null}</BoolContext.Consumer>
<FuncContext.Consumer>{value => null}</FuncContext.Consumer>
Expand Down