Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
99 changes: 65 additions & 34 deletions packages/react-devtools-shared/src/backend/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2212,6 +2212,39 @@ export function attach(
return {instance, style};
}

// find the provider fiber that provides the value for this particular consumer
function getProviderFiber(consumer: Fiber): Fiber | null {
const type = consumer.type;
// 16.3-16.5 read from "type" because the Consumer is the actual context object.
// 16.6+ should read from "type._context" because Consumer can be different (in DEV).
// NOTE Keep in sync with getDisplayNameForFiber()
const consumerResolvedContext = type._context || type;

// Look for overridden value.
let current = consumer.return;
while (current !== null) {
const currentType = current.type;
const currentTypeSymbol = getTypeSymbol(currentType);
if (
currentTypeSymbol === CONTEXT_PROVIDER_NUMBER ||
currentTypeSymbol === CONTEXT_PROVIDER_SYMBOL_STRING
) {
// 16.3.0 exposed the context object as "context"
// PR #12501 changed it to "_context" for 16.3.1+
// NOTE Keep in sync with getDisplayNameForFiber()
const providerResolvedContext =
currentType._context || currentType.context;
if (providerResolvedContext === consumerResolvedContext) {
return current;
}
}

current = current.return;
}

return null;
}

function inspectElementRaw(id: number): InspectedElement | null {
let fiber = findCurrentFiberUsingSlowPathById(id);
if (fiber == null) {
Expand Down Expand Up @@ -2270,30 +2303,12 @@ export function attach(
// NOTE Keep in sync with getDisplayNameForFiber()
const consumerResolvedContext = type._context || type;

const providerFiber = getProviderFiber(fiber);
// Global context value.
context = consumerResolvedContext._currentValue || null;

// Look for overridden value.
let current = ((fiber: any): Fiber).return;
while (current !== null) {
const currentType = current.type;
const currentTypeSymbol = getTypeSymbol(currentType);
if (
currentTypeSymbol === CONTEXT_PROVIDER_NUMBER ||
currentTypeSymbol === CONTEXT_PROVIDER_SYMBOL_STRING
) {
// 16.3.0 exposed the context object as "context"
// PR #12501 changed it to "_context" for 16.3.1+
// NOTE Keep in sync with getDisplayNameForFiber()
const providerResolvedContext =
currentType._context || currentType.context;
if (providerResolvedContext === consumerResolvedContext) {
context = current.memoizedProps.value;
break;
}
}

current = current.return;
if (providerFiber === null) {
context = consumerResolvedContext._currentValue || null;
} else {
context = providerFiber.memoizedProps.value;
}
}

Expand Down Expand Up @@ -2715,21 +2730,37 @@ export function attach(
}

function setInContext(id: number, path: Array<string | number>, value: any) {
// To simplify hydration and display of primative context values (e.g. number, string)
// the inspectElement() method wraps context in a {value: ...} object.
// We need to remove the first part of the path (the "value") before continuing.
path = path.slice(1);

const fiber = findCurrentFiberUsingSlowPathById(id);
if (fiber !== null) {
const instance = fiber.stateNode;
if (path.length === 0) {
// Simple context value
instance.context = value;
const typeSymbol = getTypeSymbol(fiber.type);
if (
typeSymbol === CONTEXT_CONSUMER_NUMBER ||
typeSymbol === CONTEXT_CONSUMER_SYMBOL_STRING
) {
// treat this like an update to the `value` prop of a Context.Provider

const providerFiber = getProviderFiber(fiber);

// if the edited context value was the default value of the context
// there is no Context.Provider above the consumer fiber
if (providerFiber !== null && typeof overrideProps === 'function') {
overrideProps(providerFiber, path, value);
}
} else {
setInObject(instance.context, path, value);
// To simplify hydration and display of primative context values (e.g. number, string)
// the inspectElement() method wraps context in a {value: ...} object.
// We need to remove the first part of the path (the "value") before continuing.
path = path.slice(1);

const instance = fiber.stateNode;
if (path.length === 0) {
// Simple context value
instance.context = value;
} else {
setInObject(instance.context, path, value);
}
instance.forceUpdate();
}
instance.forceUpdate();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
ElementTypeFunction,
ElementTypeMemo,
ElementTypeSuspense,
ElementTypeContext,
} from 'react-devtools-shared/src/types';

import styles from './SelectedElement.css';
Expand Down Expand Up @@ -362,6 +363,13 @@ function InspectedElementView({
});
}
};
} else if (type === ElementTypeContext) {
overrideContextFn = (path: Array<string | number>, value: any) => {
const rendererID = store.getRendererIDForElement(id);
if (rendererID !== null) {
bridge.send('overrideContext', {id, path, rendererID, value});
}
};
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,23 @@ export default function Contexts() {
<LegacyContextConsumer />
</LegacyContextProvider>
<ModernContext.Provider value={contextData}>
<ModernContext.Consumer>{value => null}</ModernContext.Consumer>
<ModernContext.Consumer>
{value => {
return (
<React.Fragment>
<pre>{JSON.stringify(value, null, 2)}</pre>
<ModernContext.Provider
value={{nested: true, array: ['a', 'b', 'c']}}>
<ModernContext.Consumer>
{nestedValue => (
<pre>{JSON.stringify(nestedValue, null, 2)}</pre>
)}
</ModernContext.Consumer>
</ModernContext.Provider>
</React.Fragment>
);
}}
</ModernContext.Consumer>
<ModernContextType />
</ModernContext.Provider>
<FunctionalContextConsumer />
Expand Down