Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DevTools] Implemented re-render mechanism for React 16.9+ #17072

Closed
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/react-devtools-shared/src/backend/agent.js
Expand Up @@ -125,6 +125,7 @@ export default class Agent extends EventEmitter<{|
bridge.addListener('getOwnersList', this.getOwnersList);
bridge.addListener('inspectElement', this.inspectElement);
bridge.addListener('logElementToConsole', this.logElementToConsole);
bridge.addListener('forceRerender', this.forceRerender);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: Maybe just "forceRender" ? The "re" seems unnecessary.

bridge.addListener('overrideContext', this.overrideContext);
bridge.addListener('overrideHookState', this.overrideHookState);
bridge.addListener('overrideProps', this.overrideProps);
Expand Down Expand Up @@ -255,6 +256,15 @@ export default class Agent extends EventEmitter<{|
}
};

forceRerender = ({id, rendererID}: ElementAndRendererID) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
renderer.forceRerender(id);
}
};

reloadAndProfile = (recordChangeDescriptions: boolean) => {
sessionStorageSetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, 'true');
sessionStorageSetItem(
Expand Down
7 changes: 7 additions & 0 deletions packages/react-devtools-shared/src/backend/legacy/renderer.js
Expand Up @@ -753,6 +753,9 @@ export function attach(
// Can view component source location.
canViewSource: type === ElementTypeClass || type === ElementTypeFunction,

// Can force the component to re-render.
canForceRerender: false,

// Only legacy context exists in legacy versions.
hasLegacyContext: true,

Expand Down Expand Up @@ -891,6 +894,9 @@ export function attach(
const setInHook = () => {
throw new Error('setInHook not supported by this renderer');
};
const forceRerender = () => {
throw new Error('forceRerender not supported by this renderer');
};
const startProfiling = () => {
// Do not throw, since this would break a multi-root scenario where v15 and v16 were both present.
};
Expand Down Expand Up @@ -938,6 +944,7 @@ export function attach(
handleCommitFiberUnmount,
inspectElement,
logElementToConsole,
forceRerender,
overrideSuspense,
prepareViewElementSource,
renderer,
Expand Down
18 changes: 18 additions & 0 deletions packages/react-devtools-shared/src/backend/renderer.js
Expand Up @@ -2115,6 +2115,7 @@ export function attach(
const typeSymbol = getTypeSymbol(type);

let canViewSource = false;
let canForceRerender = false;
let context = null;
if (
tag === ClassComponent ||
Expand All @@ -2126,6 +2127,11 @@ export function attach(
tag === SimpleMemoComponent
) {
canViewSource = true;

if (typeof scheduleUpdate === 'function') {
canForceRerender = true;
}

if (stateNode && stateNode.context != null) {
// Don't show an empty context object for class components that don't use the context API.
const shouldHideContext =
Expand Down Expand Up @@ -2247,6 +2253,9 @@ export function attach(
// Can view component source location.
canViewSource,

// Can force the component to re-render.
canForceRerender,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be conditional based on the element type. Right now it shows up for all types, including e.g. context providers and consumers, etc- re-rendering these doesn't make sense. Seems like this should be for class, function, forward ref, and maybe memo components types only?


// Does the component have legacy context attached to it.
hasLegacyContext,

Expand Down Expand Up @@ -2517,6 +2526,14 @@ export function attach(
}
}

function forceRerender(id: number) {
const fiber = findCurrentFiberUsingSlowPathById(id);

if (fiber !== null && typeof scheduleUpdate === 'function') {
scheduleUpdate(fiber);
}
}

function setInHook(
id: number,
index: number,
Expand Down Expand Up @@ -3029,6 +3046,7 @@ export function attach(
handleCommitFiberUnmount,
inspectElement,
logElementToConsole,
forceRerender,
prepareViewElementSource,
overrideSuspense,
renderer,
Expand Down
4 changes: 4 additions & 0 deletions packages/react-devtools-shared/src/backend/types.js
Expand Up @@ -166,6 +166,9 @@ export type InspectedElement = {|
// Can view component source location.
canViewSource: boolean,

// Can force the component to re-render.
canForceRerender: boolean,

// Does the component have legacy context attached to it.
hasLegacyContext: boolean,

Expand Down Expand Up @@ -242,6 +245,7 @@ export type RendererInterface = {
logElementToConsole: (id: number) => void,
overrideSuspense: (id: number, forceFallback: boolean) => void,
prepareViewElementSource: (id: number) => void,
forceRerender: (id: number) => void,
renderer: ReactRenderer | null,
setInContext: (id: number, path: Array<string | number>, value: any) => void,
setInHook: (
Expand Down
1 change: 1 addition & 0 deletions packages/react-devtools-shared/src/bridge.js
Expand Up @@ -100,6 +100,7 @@ type FrontendEvents = {|
highlightNativeElement: [HighlightElementInDOM],
inspectElement: [InspectElementParams],
logElementToConsole: [ElementAndRendererID],
forceRerender: [ElementAndRendererID],
overrideContext: [OverrideValue],
overrideHookState: [OverrideHookState],
overrideProps: [OverrideValue],
Expand Down
Expand Up @@ -159,6 +159,7 @@ function InspectedElementContextController({children}: Props) {
canEditHooks,
canToggleSuspense,
canViewSource,
canForceRerender,
hasLegacyContext,
source,
type,
Expand All @@ -174,6 +175,7 @@ function InspectedElementContextController({children}: Props) {
canEditHooks,
canToggleSuspense,
canViewSource,
canForceRerender,
hasLegacyContext,
id,
source,
Expand Down
Expand Up @@ -93,6 +93,20 @@ export default function SelectedElement(_: Props) {
},
[bridge, inspectedElementID, store],
);
const forceRerender = useCallback(
() => {
if (inspectedElementID !== null) {
const rendererID = store.getRendererIDForElement(inspectedElementID);
if (rendererID !== null) {
bridge.send('forceRerender', {
id: inspectedElementID,
rendererID,
});
}
}
},
[bridge, inspectedElementID, store],
);

const viewSource = useCallback(
() => {
Expand All @@ -115,6 +129,9 @@ export default function SelectedElement(_: Props) {
(canViewElementSourceFunction === null ||
canViewElementSourceFunction(inspectedElement));

const canForceRerender =
inspectedElement !== null && inspectedElement.canForceRerender;

const isSuspended =
element !== null &&
element.type === ElementTypeSuspense &&
Expand Down Expand Up @@ -227,6 +244,14 @@ export default function SelectedElement(_: Props) {
title="View source for this element">
<ButtonIcon type="view-source" />
</Button>
{canForceRerender && (
<Button
className={styles.IconButton}
onClick={forceRerender}
title="Re-render component">
<ButtonIcon type="reload" />
</Button>
)}
</div>

{inspectedElement === null && (
Expand Down
Expand Up @@ -71,6 +71,9 @@ export type InspectedElement = {|
// Can view component source location.
canViewSource: boolean,

// Can force the component to re-render.
canForceRerender: boolean,

// Does the component have legacy context attached to it.
hasLegacyContext: boolean,

Expand Down