Skip to content

Commit

Permalink
Added support for editable hook values (pending facebook/react/pull/1…
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Vaughn committed Feb 20, 2019
1 parent 90f9837 commit 1a1bb85
Show file tree
Hide file tree
Showing 13 changed files with 208 additions and 48 deletions.
2 changes: 1 addition & 1 deletion shells/browser/shared/src/panels/utils.js
@@ -1,5 +1,5 @@
import { createElement } from 'react';
import { createRoot, flushSync } from 'react-dom';
import { unstable_createRoot as createRoot, flushSync } from 'react-dom';
import DevTools from 'src/devtools/views/DevTools';
import { getBrowserName, getBrowserTheme } from '../utils';

Expand Down
2 changes: 1 addition & 1 deletion shells/dev/src/devtools.js
Expand Up @@ -2,7 +2,7 @@

import { createElement } from 'react';
// $FlowFixMe Flow does not yet know about createRoot()
import { createRoot } from 'react-dom';
import { unstable_createRoot as createRoot } from 'react-dom';
import Bridge from 'src/bridge';
import { installHook } from 'src/hook';
import { initDevTools } from 'src/devtools';
Expand Down
10 changes: 7 additions & 3 deletions src/backend/ReactDebugHooks.js
Expand Up @@ -225,6 +225,7 @@ type ReactCurrentDispatcher = {
};

type HooksNode = {
nativeHookIndex: number,
name: string,
value: mixed,
subHooks: Array<HooksNode>,
Expand Down Expand Up @@ -366,6 +367,7 @@ function buildTree(rootStack, readHookLog): HooksTree {
const rootChildren = [];
let prevStack = null;
let levelChildren = rootChildren;
let nativeHookIndex = 0;
const stackOfChildren = [];
for (let i = 0; i < readHookLog.length; i++) {
const hook = readHookLog[i];
Expand Down Expand Up @@ -399,6 +401,7 @@ function buildTree(rootStack, readHookLog): HooksTree {
levelChildren.push({
name: parseCustomHookName(stack[j - 1].functionName),
value: undefined,
nativeHookIndex: -1,
subHooks: children,
});
stackOfChildren.push(levelChildren);
Expand All @@ -409,12 +412,13 @@ function buildTree(rootStack, readHookLog): HooksTree {
levelChildren.push({
name: hook.primitive,
value: hook.value,
nativeHookIndex: hook.primitive === 'DebugValue' ? -1 : nativeHookIndex++,
subHooks: [],
});
}

// Associate custom hook values (useDebugValue() hook entries) with the correct hooks.
rollupDebugValues(rootChildren, null);
processDebugValues(rootChildren, null);

return rootChildren;
}
Expand All @@ -423,7 +427,7 @@ function buildTree(rootStack, readHookLog): HooksTree {
// That hook adds the user-provided values to the hooks tree.
// This method removes those values (so they don't appear in DevTools),
// and bubbles them up to the "value" attribute of their parent custom hook.
function rollupDebugValues(
function processDebugValues(
hooksTree: HooksTree,
parentHooksNode: HooksNode | null
): void {
Expand All @@ -436,7 +440,7 @@ function rollupDebugValues(
i--;
debugValueHooksNodes.push(hooksNode);
} else {
rollupDebugValues(hooksNode.subHooks, hooksNode);
processDebugValues(hooksNode.subHooks, hooksNode);
}
}

Expand Down
24 changes: 24 additions & 0 deletions src/backend/agent.js
Expand Up @@ -23,6 +23,14 @@ type InspectSelectParams = {|
rendererID: number,
|};

type OverrideHookParams = {|
id: number,
nativeHookIndex: number,
path: Array<string | number>,
rendererID: number,
value: any,
|};

type SetInParams = {|
id: number,
path: Array<string | number>,
Expand All @@ -40,6 +48,7 @@ export default class Agent extends EventEmitter {
bridge.addListener('highlightElementInDOM', this.highlightElementInDOM);
bridge.addListener('inspectElement', this.inspectElement);
bridge.addListener('overrideContext', this.overrideContext);
bridge.addListener('overrideHook', this.overrideHook);
bridge.addListener('overrideProps', this.overrideProps);
bridge.addListener('overrideState', this.overrideState);
bridge.addListener('selectElement', this.selectElement);
Expand Down Expand Up @@ -122,6 +131,21 @@ export default class Agent extends EventEmitter {
}
};

overrideHook = ({
id,
nativeHookIndex,
path,
rendererID,
value,
}: OverrideHookParams) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
renderer.setInHook(id, nativeHookIndex, path, value);
}
};

overrideProps = ({ id, path, rendererID, value }: SetInParams) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
Expand Down
8 changes: 6 additions & 2 deletions src/backend/index.js
@@ -1,11 +1,15 @@
// @flow

import type { Hook, ReactRenderer, RendererInterface } from './types';
import type { DevToolsHook, ReactRenderer, RendererInterface } from './types';
import Agent from './agent';

import { attach } from './renderer';

export function initBackend(hook: Hook, agent: Agent, global: Object): void {
export function initBackend(
hook: DevToolsHook,
agent: Agent,
global: Object
): void {
const subs = [
hook.sub(
'renderer-attached',
Expand Down
26 changes: 22 additions & 4 deletions src/backend/renderer.js
Expand Up @@ -24,8 +24,8 @@ import { getUID } from '../utils';
import { inspectHooksOfFiber } from './ReactDebugHooks';

import type {
DevToolsHook,
Fiber,
Hook,
ReactRenderer,
FiberData,
RendererInterface,
Expand Down Expand Up @@ -151,7 +151,7 @@ function getInternalReactConstants(version) {
}

export function attach(
hook: Hook,
hook: DevToolsHook,
rendererID: number,
renderer: ReactRenderer,
global: Object
Expand Down Expand Up @@ -194,7 +194,7 @@ export function attach(
DEPRECATED_PLACEHOLDER_SYMBOL_STRING,
} = ReactSymbols;

const { overrideProps } = renderer;
const { overrideHook, overrideProps } = renderer;

const debug = (name: string, fiber: Fiber, parentFiber: ?Fiber): void => {
if (__DEBUG__) {
Expand Down Expand Up @@ -1141,7 +1141,10 @@ export function attach(
return {
id,

// Does the current renderer support editable props/state/hooks?
// Does the current renderer support editable hooks?
canEditHooks: typeof overrideHook === 'function',

// Does the current renderer support editable function props?
canEditFunctionProps: typeof overrideProps === 'function',

// Inspectable properties.
Expand All @@ -1163,6 +1166,20 @@ export function attach(
};
}

function setInHook(
id: number,
nativeHookIndex: number,
path: Array<string | number>,
value: any
) {
const fiber = findCurrentFiberUsingSlowPath(idToFiberMap.get(id));
if (fiber !== null) {
if (typeof overrideHook === 'function') {
overrideHook(fiber, nativeHookIndex, path, value);
}
}
}

function setInProps(id: number, path: Array<string | number>, value: any) {
const fiber = findCurrentFiberUsingSlowPath(idToFiberMap.get(id));
if (fiber !== null) {
Expand Down Expand Up @@ -1216,6 +1233,7 @@ export function attach(
cleanup,
renderer,
setInContext,
setInHook,
setInProps,
setInState,
walkTree,
Expand Down
21 changes: 19 additions & 2 deletions src/backend/types.js
Expand Up @@ -33,6 +33,16 @@ export type ReactRenderer = {
findFiberByHostInstance: (hostInstance: NativeType) => ?Fiber,
version: string,
bundleType: BundleType,

// 16.9+
overrideHook?: ?(
fiber: Object,
nativeHookIndex: number,
path: Array<string | number>,
value: any
) => void,

// 16.7+
overrideProps?: ?(
fiber: Object,
path: Array<string | number>,
Expand All @@ -55,15 +65,21 @@ export type RendererInterface = {
inspectElement: (id: number) => InspectedElement | null,
renderer: ReactRenderer | null,
selectElement: (id: number) => void,
setInContext: (id: number, path: Array<string | number>, value: any) => void,
setInHook: (
id: number,
nativeHookIndex: number,
path: Array<string | number>,
value: any
) => void,
setInProps: (id: number, path: Array<string | number>, value: any) => void,
setInState: (id: number, path: Array<string | number>, value: any) => void,
setInContext: (id: number, path: Array<string | number>, value: any) => void,
walkTree: () => void,
};

export type Handler = (data: any) => void;

export type Hook = {
export type DevToolsHook = {
listeners: { [key: string]: Array<Handler> },
rendererInterfaces: Map<RendererID, RendererInterface>,
renderers: Map<RendererID, ReactRenderer>,
Expand All @@ -83,6 +99,7 @@ export type Hook = {
};

export type HooksNode = {
nativeHookIndex: number,
name: string,
value: mixed,
subHooks: Array<HooksNode>,
Expand Down
3 changes: 3 additions & 0 deletions src/devtools/types.js
Expand Up @@ -48,6 +48,9 @@ export type Owner = {|
export type InspectedElement = {|
id: number,

// Does the current renderer support editable hooks?
canEditHooks: boolean,

// Does the current renderer support editable function props?
canEditFunctionProps: boolean,

Expand Down
9 changes: 8 additions & 1 deletion src/devtools/views/HooksTree.css
Expand Up @@ -3,15 +3,22 @@
border-top: 1px solid var(--color-border);
}

.HooksNode {
.Hook {
padding-left: 0.75rem;
}

.NameValueRow {
display: flex;
}

.Name {
color: var(--color-attribute-name);
flex: 0 0 auto;
}
.Name:after {
content: ': ';
color: var(--color-text-color);
margin-right: 0.5rem;
}

.Value {
Expand Down

0 comments on commit 1a1bb85

Please sign in to comment.