From dc2b45fbcb9a42d76fcf8e77a0e350c6a1238e01 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 20 Feb 2019 15:04:47 -0800 Subject: [PATCH 01/10] Inject overrideHook() method to DevTools --- .../src/ReactFiberReconciler.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index acb91ea3bb905..6ae7a264363e5 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -341,6 +341,7 @@ export function findHostInstanceWithNoPortals( return hostFiber.stateNode; } +let overrideHook = null; let overrideProps = null; if (__DEV__) { @@ -368,6 +369,24 @@ if (__DEV__) { return copyWithSetImpl(obj, path, 0, value); }; + // Support DevTools editable hooks state. + overrideHook = ( + fiber: Fiber, + nativeHookIndex: number, + path: Array, + value: any, + ) => { + let currentHook = fiber.memoizedState; + while (currentHook !== null && nativeHookIndex > 0) { + currentHook = currentHook.next; + nativeHookIndex--; + } + if (currentHook !== null) { + let updatedState = copyWithSet(currentHook.memoizedState, path, value); + currentHook.queue.dispatch(updatedState); + } + }; + // Support DevTools props for function components, forwardRef, memo, host components, etc. overrideProps = (fiber: Fiber, path: Array, value: any) => { flushPassiveEffects(); @@ -385,6 +404,7 @@ export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean { return injectInternals({ ...devToolsConfig, + overrideHook, overrideProps, currentDispatcherRef: ReactCurrentDispatcher, findHostInstanceByFiber(fiber: Fiber): Instance | TextInstance | null { From 6ee31b0261b440f5cad2044a421e50a697bd7259 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 20 Feb 2019 15:05:04 -0800 Subject: [PATCH 02/10] Identify native hook index (for DevTools editable hooks) --- .../react-debug-tools/src/ReactDebugHooks.js | 3 + .../__tests__/ReactHooksInspection-test.js | 25 +++- .../ReactHooksInspectionIntegration-test.js | 112 ++++++++++++------ 3 files changed, 105 insertions(+), 35 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 2dc1ddb02cac2..e96c767f36d84 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -373,6 +373,7 @@ function buildTree(rootStack, readHookLog): HooksTree { let rootChildren = []; let prevStack = null; let levelChildren = rootChildren; + let nativeHookIndex = 0; let stackOfChildren = []; for (let i = 0; i < readHookLog.length; i++) { let hook = readHookLog[i]; @@ -403,6 +404,7 @@ function buildTree(rootStack, readHookLog): HooksTree { for (let j = stack.length - commonSteps - 1; j >= 1; j--) { let children = []; levelChildren.push({ + nativeHookIndex: -1, name: parseCustomHookName(stack[j - 1].functionName), value: undefined, subHooks: children, @@ -413,6 +415,7 @@ function buildTree(rootStack, readHookLog): HooksTree { prevStack = stack; } levelChildren.push({ + nativeHookIndex: hook.primitive === 'DebugValue' ? -1 : nativeHookIndex++, name: hook.primitive, value: hook.value, subHooks: [], diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js index b340779a74d10..d37a42dc90ff7 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js @@ -28,6 +28,7 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { + nativeHookIndex: 0, name: 'State', value: 'hello world', subHooks: [], @@ -48,10 +49,12 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { + nativeHookIndex: -1, name: 'Custom', value: __DEV__ ? 'custom hook label' : undefined, subHooks: [ { + nativeHookIndex: 0, name: 'State', value: 'hello world', subHooks: [], @@ -80,15 +83,18 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { + nativeHookIndex: -1, name: 'Custom', value: undefined, subHooks: [ { + nativeHookIndex: 0, name: 'State', subHooks: [], value: 'hello', }, { + nativeHookIndex: 1, name: 'Effect', subHooks: [], value: effect, @@ -96,15 +102,18 @@ describe('ReactHooksInspection', () => { ], }, { + nativeHookIndex: -1, name: 'Custom', value: undefined, subHooks: [ { + nativeHookIndex: 2, name: 'State', value: 'world', subHooks: [], }, { + nativeHookIndex: 3, name: 'Effect', value: effect, subHooks: [], @@ -143,19 +152,23 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { + nativeHookIndex: -1, name: 'Bar', value: undefined, subHooks: [ { + nativeHookIndex: -1, name: 'Custom', value: undefined, subHooks: [ { + nativeHookIndex: 0, name: 'Reducer', value: 'hello', subHooks: [], }, { + nativeHookIndex: 1, name: 'Effect', value: effect, subHooks: [], @@ -163,6 +176,7 @@ describe('ReactHooksInspection', () => { ], }, { + nativeHookIndex: 2, name: 'LayoutEffect', value: effect, subHooks: [], @@ -170,23 +184,28 @@ describe('ReactHooksInspection', () => { ], }, { + nativeHookIndex: -1, name: 'Baz', value: undefined, subHooks: [ { + nativeHookIndex: 3, name: 'LayoutEffect', value: effect, subHooks: [], }, { + nativeHookIndex: -1, name: 'Custom', subHooks: [ { + nativeHookIndex: 4, name: 'Reducer', subHooks: [], value: 'world', }, { + nativeHookIndex: 5, name: 'Effect', subHooks: [], value: effect, @@ -208,6 +227,7 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { + nativeHookIndex: 0, name: 'Context', value: 'default', subHooks: [], @@ -270,9 +290,12 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { + nativeHookIndex: -1, name: 'Custom', value: __DEV__ ? 'bar:123' : undefined, - subHooks: [{name: 'State', subHooks: [], value: 0}], + subHooks: [ + {nativeHookIndex: 0, name: 'State', subHooks: [], value: 0}, + ], }, ]); }); diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index 8c56c8cd775ac..3cbb2c0794dc0 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -40,8 +40,8 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root.findByType(Foo)._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {name: 'State', value: 'hello', subHooks: []}, - {name: 'State', value: 'world', subHooks: []}, + {nativeHookIndex: 0, name: 'State', value: 'hello', subHooks: []}, + {nativeHookIndex: 1, name: 'State', value: 'world', subHooks: []}, ]); let { @@ -55,8 +55,8 @@ describe('ReactHooksInspectionIntegration', () => { tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {name: 'State', value: 'Hi', subHooks: []}, - {name: 'State', value: 'world', subHooks: []}, + {nativeHookIndex: 0, name: 'State', value: 'Hi', subHooks: []}, + {nativeHookIndex: 1, name: 'State', value: 'world', subHooks: []}, ]); act(() => setStateB('world!')); @@ -65,8 +65,8 @@ describe('ReactHooksInspectionIntegration', () => { tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {name: 'State', value: 'Hi', subHooks: []}, - {name: 'State', value: 'world!', subHooks: []}, + {nativeHookIndex: 0, name: 'State', value: 'Hi', subHooks: []}, + {nativeHookIndex: 1, name: 'State', value: 'world!', subHooks: []}, ]); }); @@ -116,14 +116,19 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {name: 'State', value: 'a', subHooks: []}, - {name: 'Reducer', value: 'b', subHooks: []}, - {name: 'Ref', value: 'c', subHooks: []}, - {name: 'LayoutEffect', value: effect, subHooks: []}, - {name: 'Effect', value: effect, subHooks: []}, - {name: 'ImperativeHandle', value: outsideRef.current, subHooks: []}, - {name: 'Memo', value: 'ab', subHooks: []}, - {name: 'Callback', value: updateStates, subHooks: []}, + {nativeHookIndex: 0, name: 'State', value: 'a', subHooks: []}, + {nativeHookIndex: 1, name: 'Reducer', value: 'b', subHooks: []}, + {nativeHookIndex: 2, name: 'Ref', value: 'c', subHooks: []}, + {nativeHookIndex: 3, name: 'LayoutEffect', value: effect, subHooks: []}, + {nativeHookIndex: 4, name: 'Effect', value: effect, subHooks: []}, + { + nativeHookIndex: 5, + name: 'ImperativeHandle', + value: outsideRef.current, + subHooks: [], + }, + {nativeHookIndex: 6, name: 'Memo', value: 'ab', subHooks: []}, + {nativeHookIndex: 7, name: 'Callback', value: updateStates, subHooks: []}, ]); updateStates(); @@ -132,14 +137,19 @@ describe('ReactHooksInspectionIntegration', () => { tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {name: 'State', value: 'A', subHooks: []}, - {name: 'Reducer', value: 'B', subHooks: []}, - {name: 'Ref', value: 'C', subHooks: []}, - {name: 'LayoutEffect', value: effect, subHooks: []}, - {name: 'Effect', value: effect, subHooks: []}, - {name: 'ImperativeHandle', value: outsideRef.current, subHooks: []}, - {name: 'Memo', value: 'Ab', subHooks: []}, - {name: 'Callback', value: updateStates, subHooks: []}, + {nativeHookIndex: 0, name: 'State', value: 'A', subHooks: []}, + {nativeHookIndex: 1, name: 'Reducer', value: 'B', subHooks: []}, + {nativeHookIndex: 2, name: 'Ref', value: 'C', subHooks: []}, + {nativeHookIndex: 3, name: 'LayoutEffect', value: effect, subHooks: []}, + {nativeHookIndex: 4, name: 'Effect', value: effect, subHooks: []}, + { + nativeHookIndex: 5, + name: 'ImperativeHandle', + value: outsideRef.current, + subHooks: [], + }, + {nativeHookIndex: 6, name: 'Memo', value: 'Ab', subHooks: []}, + {nativeHookIndex: 7, name: 'Callback', value: updateStates, subHooks: []}, ]); }); @@ -158,6 +168,7 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { + nativeHookIndex: 0, name: 'Context', value: 'contextual', subHooks: [], @@ -177,7 +188,7 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root.findByType(Foo)._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {name: 'ImperativeHandle', value: obj, subHooks: []}, + {nativeHookIndex: 0, name: 'ImperativeHandle', value: obj, subHooks: []}, ]); }); @@ -191,7 +202,9 @@ describe('ReactHooksInspectionIntegration', () => { // TODO: Test renderer findByType is broken for memo. Have to search for the inner. let childFiber = renderer.root.findByType(InnerFoo)._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); - expect(tree).toEqual([{name: 'State', value: 'hello', subHooks: []}]); + expect(tree).toEqual([ + {nativeHookIndex: 0, name: 'State', value: 'hello', subHooks: []}, + ]); }); it('should inspect custom hooks', () => { @@ -208,9 +221,12 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { + nativeHookIndex: -1, name: 'Custom', value: undefined, - subHooks: [{name: 'State', value: 'hello', subHooks: []}], + subHooks: [ + {nativeHookIndex: 0, name: 'State', value: 'hello', subHooks: []}, + ], }, ]); }); @@ -238,24 +254,34 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { + nativeHookIndex: -1, name: 'LabeledValue', value: __DEV__ ? 'custom label a' : undefined, - subHooks: [{name: 'State', value: 'a', subHooks: []}], + subHooks: [ + {nativeHookIndex: 0, name: 'State', value: 'a', subHooks: []}, + ], }, { + nativeHookIndex: 1, name: 'State', value: 'b', subHooks: [], }, { + nativeHookIndex: -1, name: 'Anonymous', value: undefined, - subHooks: [{name: 'State', value: 'c', subHooks: []}], + subHooks: [ + {nativeHookIndex: 2, name: 'State', value: 'c', subHooks: []}, + ], }, { + nativeHookIndex: -1, name: 'LabeledValue', value: __DEV__ ? 'custom label d' : undefined, - subHooks: [{name: 'State', value: 'd', subHooks: []}], + subHooks: [ + {nativeHookIndex: 3, name: 'State', value: 'd', subHooks: []}, + ], }, ]); }); @@ -278,13 +304,17 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { + nativeHookIndex: -1, name: 'Outer', value: __DEV__ ? 'outer' : undefined, subHooks: [ { + nativeHookIndex: -1, name: 'Inner', value: __DEV__ ? 'inner' : undefined, - subHooks: [{name: 'State', value: 0, subHooks: []}], + subHooks: [ + {nativeHookIndex: 0, name: 'State', value: 0, subHooks: []}, + ], }, ], }, @@ -313,19 +343,28 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { + nativeHookIndex: -1, name: 'SingleLabelCustom', value: __DEV__ ? 'single one' : undefined, - subHooks: [{name: 'State', value: 0, subHooks: []}], + subHooks: [ + {nativeHookIndex: 0, name: 'State', value: 0, subHooks: []}, + ], }, { + nativeHookIndex: -1, name: 'MultiLabelCustom', value: __DEV__ ? ['one', 'two', 'three'] : undefined, - subHooks: [{name: 'State', value: 0, subHooks: []}], + subHooks: [ + {nativeHookIndex: 1, name: 'State', value: 0, subHooks: []}, + ], }, { + nativeHookIndex: -1, name: 'SingleLabelCustom', value: __DEV__ ? 'single two' : undefined, - subHooks: [{name: 'State', value: 0, subHooks: []}], + subHooks: [ + {nativeHookIndex: 2, name: 'State', value: 0, subHooks: []}, + ], }, ]); }); @@ -355,9 +394,12 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { + nativeHookIndex: -1, name: 'Custom', value: __DEV__ ? 'bar:123' : undefined, - subHooks: [{name: 'State', subHooks: [], value: 0}], + subHooks: [ + {nativeHookIndex: 0, name: 'State', subHooks: [], value: 0}, + ], }, ]); }); @@ -390,7 +432,9 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); - expect(tree).toEqual([{name: 'State', value: 'def', subHooks: []}]); + expect(tree).toEqual([ + {nativeHookIndex: 0, name: 'State', value: 'def', subHooks: []}, + ]); }); it('should support an injected dispatcher', () => { From ccc895fa1182346a87d241008e4db2ab9b0a2068 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 20 Feb 2019 15:15:49 -0800 Subject: [PATCH 03/10] Added nativeHookIndex to HooksNode type --- packages/react-debug-tools/src/ReactDebugHooks.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index e96c767f36d84..44930e549978d 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -232,6 +232,7 @@ const Dispatcher: DispatcherType = { // Inspect type HooksNode = { + nativeHookIndex: number, name: string, value: mixed, subHooks: Array, From bdba5d1704051bc8844d802d6015a20497da81b4 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 21 Feb 2019 08:45:55 -0800 Subject: [PATCH 04/10] Renamed nativeHookIndex -> index --- .../react-debug-tools/src/ReactDebugHooks.js | 8 +- .../__tests__/ReactHooksInspection-test.js | 46 ++++---- .../ReactHooksInspectionIntegration-test.js | 110 ++++++++---------- .../src/ReactFiberReconciler.js | 6 +- 4 files changed, 75 insertions(+), 95 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 44930e549978d..5da57d87b3fff 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -232,7 +232,7 @@ const Dispatcher: DispatcherType = { // Inspect type HooksNode = { - nativeHookIndex: number, + index: number, name: string, value: mixed, subHooks: Array, @@ -374,7 +374,7 @@ function buildTree(rootStack, readHookLog): HooksTree { let rootChildren = []; let prevStack = null; let levelChildren = rootChildren; - let nativeHookIndex = 0; + let index = 0; let stackOfChildren = []; for (let i = 0; i < readHookLog.length; i++) { let hook = readHookLog[i]; @@ -405,7 +405,7 @@ function buildTree(rootStack, readHookLog): HooksTree { for (let j = stack.length - commonSteps - 1; j >= 1; j--) { let children = []; levelChildren.push({ - nativeHookIndex: -1, + index: -1, name: parseCustomHookName(stack[j - 1].functionName), value: undefined, subHooks: children, @@ -416,7 +416,7 @@ function buildTree(rootStack, readHookLog): HooksTree { prevStack = stack; } levelChildren.push({ - nativeHookIndex: hook.primitive === 'DebugValue' ? -1 : nativeHookIndex++, + index: hook.primitive === 'DebugValue' ? -1 : index++, name: hook.primitive, value: hook.value, subHooks: [], diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js index d37a42dc90ff7..870bd19f3f6cd 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js @@ -28,7 +28,7 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { - nativeHookIndex: 0, + index: 0, name: 'State', value: 'hello world', subHooks: [], @@ -49,12 +49,12 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { - nativeHookIndex: -1, + index: -1, name: 'Custom', value: __DEV__ ? 'custom hook label' : undefined, subHooks: [ { - nativeHookIndex: 0, + index: 0, name: 'State', value: 'hello world', subHooks: [], @@ -83,18 +83,18 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { - nativeHookIndex: -1, + index: -1, name: 'Custom', value: undefined, subHooks: [ { - nativeHookIndex: 0, + index: 0, name: 'State', subHooks: [], value: 'hello', }, { - nativeHookIndex: 1, + index: 1, name: 'Effect', subHooks: [], value: effect, @@ -102,18 +102,18 @@ describe('ReactHooksInspection', () => { ], }, { - nativeHookIndex: -1, + index: -1, name: 'Custom', value: undefined, subHooks: [ { - nativeHookIndex: 2, + index: 2, name: 'State', value: 'world', subHooks: [], }, { - nativeHookIndex: 3, + index: 3, name: 'Effect', value: effect, subHooks: [], @@ -152,23 +152,23 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { - nativeHookIndex: -1, + index: -1, name: 'Bar', value: undefined, subHooks: [ { - nativeHookIndex: -1, + index: -1, name: 'Custom', value: undefined, subHooks: [ { - nativeHookIndex: 0, + index: 0, name: 'Reducer', value: 'hello', subHooks: [], }, { - nativeHookIndex: 1, + index: 1, name: 'Effect', value: effect, subHooks: [], @@ -176,7 +176,7 @@ describe('ReactHooksInspection', () => { ], }, { - nativeHookIndex: 2, + index: 2, name: 'LayoutEffect', value: effect, subHooks: [], @@ -184,28 +184,28 @@ describe('ReactHooksInspection', () => { ], }, { - nativeHookIndex: -1, + index: -1, name: 'Baz', value: undefined, subHooks: [ { - nativeHookIndex: 3, + index: 3, name: 'LayoutEffect', value: effect, subHooks: [], }, { - nativeHookIndex: -1, + index: -1, name: 'Custom', subHooks: [ { - nativeHookIndex: 4, + index: 4, name: 'Reducer', subHooks: [], value: 'world', }, { - nativeHookIndex: 5, + index: 5, name: 'Effect', subHooks: [], value: effect, @@ -227,7 +227,7 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { - nativeHookIndex: 0, + index: 0, name: 'Context', value: 'default', subHooks: [], @@ -290,12 +290,10 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { - nativeHookIndex: -1, + index: -1, name: 'Custom', value: __DEV__ ? 'bar:123' : undefined, - subHooks: [ - {nativeHookIndex: 0, name: 'State', subHooks: [], value: 0}, - ], + subHooks: [{index: 0, name: 'State', subHooks: [], value: 0}], }, ]); }); diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index 3cbb2c0794dc0..760ae4ed3f45b 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -40,8 +40,8 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root.findByType(Foo)._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {nativeHookIndex: 0, name: 'State', value: 'hello', subHooks: []}, - {nativeHookIndex: 1, name: 'State', value: 'world', subHooks: []}, + {index: 0, name: 'State', value: 'hello', subHooks: []}, + {index: 1, name: 'State', value: 'world', subHooks: []}, ]); let { @@ -55,8 +55,8 @@ describe('ReactHooksInspectionIntegration', () => { tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {nativeHookIndex: 0, name: 'State', value: 'Hi', subHooks: []}, - {nativeHookIndex: 1, name: 'State', value: 'world', subHooks: []}, + {index: 0, name: 'State', value: 'Hi', subHooks: []}, + {index: 1, name: 'State', value: 'world', subHooks: []}, ]); act(() => setStateB('world!')); @@ -65,8 +65,8 @@ describe('ReactHooksInspectionIntegration', () => { tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {nativeHookIndex: 0, name: 'State', value: 'Hi', subHooks: []}, - {nativeHookIndex: 1, name: 'State', value: 'world!', subHooks: []}, + {index: 0, name: 'State', value: 'Hi', subHooks: []}, + {index: 1, name: 'State', value: 'world!', subHooks: []}, ]); }); @@ -116,19 +116,19 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {nativeHookIndex: 0, name: 'State', value: 'a', subHooks: []}, - {nativeHookIndex: 1, name: 'Reducer', value: 'b', subHooks: []}, - {nativeHookIndex: 2, name: 'Ref', value: 'c', subHooks: []}, - {nativeHookIndex: 3, name: 'LayoutEffect', value: effect, subHooks: []}, - {nativeHookIndex: 4, name: 'Effect', value: effect, subHooks: []}, + {index: 0, name: 'State', value: 'a', subHooks: []}, + {index: 1, name: 'Reducer', value: 'b', subHooks: []}, + {index: 2, name: 'Ref', value: 'c', subHooks: []}, + {index: 3, name: 'LayoutEffect', value: effect, subHooks: []}, + {index: 4, name: 'Effect', value: effect, subHooks: []}, { - nativeHookIndex: 5, + index: 5, name: 'ImperativeHandle', value: outsideRef.current, subHooks: [], }, - {nativeHookIndex: 6, name: 'Memo', value: 'ab', subHooks: []}, - {nativeHookIndex: 7, name: 'Callback', value: updateStates, subHooks: []}, + {index: 6, name: 'Memo', value: 'ab', subHooks: []}, + {index: 7, name: 'Callback', value: updateStates, subHooks: []}, ]); updateStates(); @@ -137,19 +137,19 @@ describe('ReactHooksInspectionIntegration', () => { tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {nativeHookIndex: 0, name: 'State', value: 'A', subHooks: []}, - {nativeHookIndex: 1, name: 'Reducer', value: 'B', subHooks: []}, - {nativeHookIndex: 2, name: 'Ref', value: 'C', subHooks: []}, - {nativeHookIndex: 3, name: 'LayoutEffect', value: effect, subHooks: []}, - {nativeHookIndex: 4, name: 'Effect', value: effect, subHooks: []}, + {index: 0, name: 'State', value: 'A', subHooks: []}, + {index: 1, name: 'Reducer', value: 'B', subHooks: []}, + {index: 2, name: 'Ref', value: 'C', subHooks: []}, + {index: 3, name: 'LayoutEffect', value: effect, subHooks: []}, + {index: 4, name: 'Effect', value: effect, subHooks: []}, { - nativeHookIndex: 5, + index: 5, name: 'ImperativeHandle', value: outsideRef.current, subHooks: [], }, - {nativeHookIndex: 6, name: 'Memo', value: 'Ab', subHooks: []}, - {nativeHookIndex: 7, name: 'Callback', value: updateStates, subHooks: []}, + {index: 6, name: 'Memo', value: 'Ab', subHooks: []}, + {index: 7, name: 'Callback', value: updateStates, subHooks: []}, ]); }); @@ -168,7 +168,7 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { - nativeHookIndex: 0, + index: 0, name: 'Context', value: 'contextual', subHooks: [], @@ -188,7 +188,7 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root.findByType(Foo)._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {nativeHookIndex: 0, name: 'ImperativeHandle', value: obj, subHooks: []}, + {index: 0, name: 'ImperativeHandle', value: obj, subHooks: []}, ]); }); @@ -203,7 +203,7 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root.findByType(InnerFoo)._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {nativeHookIndex: 0, name: 'State', value: 'hello', subHooks: []}, + {index: 0, name: 'State', value: 'hello', subHooks: []}, ]); }); @@ -221,12 +221,10 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { - nativeHookIndex: -1, + index: -1, name: 'Custom', value: undefined, - subHooks: [ - {nativeHookIndex: 0, name: 'State', value: 'hello', subHooks: []}, - ], + subHooks: [{index: 0, name: 'State', value: 'hello', subHooks: []}], }, ]); }); @@ -254,34 +252,28 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { - nativeHookIndex: -1, + index: -1, name: 'LabeledValue', value: __DEV__ ? 'custom label a' : undefined, - subHooks: [ - {nativeHookIndex: 0, name: 'State', value: 'a', subHooks: []}, - ], + subHooks: [{index: 0, name: 'State', value: 'a', subHooks: []}], }, { - nativeHookIndex: 1, + index: 1, name: 'State', value: 'b', subHooks: [], }, { - nativeHookIndex: -1, + index: -1, name: 'Anonymous', value: undefined, - subHooks: [ - {nativeHookIndex: 2, name: 'State', value: 'c', subHooks: []}, - ], + subHooks: [{index: 2, name: 'State', value: 'c', subHooks: []}], }, { - nativeHookIndex: -1, + index: -1, name: 'LabeledValue', value: __DEV__ ? 'custom label d' : undefined, - subHooks: [ - {nativeHookIndex: 3, name: 'State', value: 'd', subHooks: []}, - ], + subHooks: [{index: 3, name: 'State', value: 'd', subHooks: []}], }, ]); }); @@ -304,17 +296,15 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { - nativeHookIndex: -1, + index: -1, name: 'Outer', value: __DEV__ ? 'outer' : undefined, subHooks: [ { - nativeHookIndex: -1, + index: -1, name: 'Inner', value: __DEV__ ? 'inner' : undefined, - subHooks: [ - {nativeHookIndex: 0, name: 'State', value: 0, subHooks: []}, - ], + subHooks: [{index: 0, name: 'State', value: 0, subHooks: []}], }, ], }, @@ -343,28 +333,22 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { - nativeHookIndex: -1, + index: -1, name: 'SingleLabelCustom', value: __DEV__ ? 'single one' : undefined, - subHooks: [ - {nativeHookIndex: 0, name: 'State', value: 0, subHooks: []}, - ], + subHooks: [{index: 0, name: 'State', value: 0, subHooks: []}], }, { - nativeHookIndex: -1, + index: -1, name: 'MultiLabelCustom', value: __DEV__ ? ['one', 'two', 'three'] : undefined, - subHooks: [ - {nativeHookIndex: 1, name: 'State', value: 0, subHooks: []}, - ], + subHooks: [{index: 1, name: 'State', value: 0, subHooks: []}], }, { - nativeHookIndex: -1, + index: -1, name: 'SingleLabelCustom', value: __DEV__ ? 'single two' : undefined, - subHooks: [ - {nativeHookIndex: 2, name: 'State', value: 0, subHooks: []}, - ], + subHooks: [{index: 2, name: 'State', value: 0, subHooks: []}], }, ]); }); @@ -394,12 +378,10 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { - nativeHookIndex: -1, + index: -1, name: 'Custom', value: __DEV__ ? 'bar:123' : undefined, - subHooks: [ - {nativeHookIndex: 0, name: 'State', subHooks: [], value: 0}, - ], + subHooks: [{index: 0, name: 'State', subHooks: [], value: 0}], }, ]); }); @@ -433,7 +415,7 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {nativeHookIndex: 0, name: 'State', value: 'def', subHooks: []}, + {index: 0, name: 'State', value: 'def', subHooks: []}, ]); }); diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index 6ae7a264363e5..5b45cc5894bf8 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -372,14 +372,14 @@ if (__DEV__) { // Support DevTools editable hooks state. overrideHook = ( fiber: Fiber, - nativeHookIndex: number, + index: number, path: Array, value: any, ) => { let currentHook = fiber.memoizedState; - while (currentHook !== null && nativeHookIndex > 0) { + while (currentHook !== null && index > 0) { currentHook = currentHook.next; - nativeHookIndex--; + index--; } if (currentHook !== null) { let updatedState = copyWithSet(currentHook.memoizedState, path, value); From 72241a62378568b52349dd5f2108ced60007072b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 21 Feb 2019 11:16:46 -0800 Subject: [PATCH 05/10] Changed overrideHook to support useReducer overrides; added isEditable bool to hooks metadata --- .../react-debug-tools/src/ReactDebugHooks.js | 8 +- .../__tests__/ReactHooksInspection-test.js | 25 ++- .../ReactHooksInspectionIntegration-test.js | 174 ++++++++++++++---- .../src/ReactFiberReconciler.js | 9 +- 4 files changed, 178 insertions(+), 38 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 5da57d87b3fff..1389ab5bcc2fa 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -233,6 +233,7 @@ const Dispatcher: DispatcherType = { type HooksNode = { index: number, + isEditable: boolean, name: string, value: mixed, subHooks: Array, @@ -406,6 +407,7 @@ function buildTree(rootStack, readHookLog): HooksTree { let children = []; levelChildren.push({ index: -1, + isEditable: false, name: parseCustomHookName(stack[j - 1].functionName), value: undefined, subHooks: children, @@ -415,9 +417,11 @@ function buildTree(rootStack, readHookLog): HooksTree { } prevStack = stack; } + const {primitive} = hook; levelChildren.push({ - index: hook.primitive === 'DebugValue' ? -1 : index++, - name: hook.primitive, + index: primitive === 'DebugValue' ? -1 : index++, + isEditable: primitive === 'Reducer' || primitive === 'State', + name: primitive, value: hook.value, subHooks: [], }); diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js index 870bd19f3f6cd..8121e5e4fb463 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js @@ -28,6 +28,7 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { + isEditable: true, index: 0, name: 'State', value: 'hello world', @@ -49,11 +50,13 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { + isEditable: false, index: -1, name: 'Custom', value: __DEV__ ? 'custom hook label' : undefined, subHooks: [ { + isEditable: true, index: 0, name: 'State', value: 'hello world', @@ -83,17 +86,20 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { + isEditable: false, index: -1, name: 'Custom', value: undefined, subHooks: [ { + isEditable: true, index: 0, name: 'State', subHooks: [], value: 'hello', }, { + isEditable: false, index: 1, name: 'Effect', subHooks: [], @@ -102,17 +108,20 @@ describe('ReactHooksInspection', () => { ], }, { + isEditable: false, index: -1, name: 'Custom', value: undefined, subHooks: [ { + isEditable: true, index: 2, name: 'State', value: 'world', subHooks: [], }, { + isEditable: false, index: 3, name: 'Effect', value: effect, @@ -152,22 +161,26 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { + isEditable: false, index: -1, name: 'Bar', value: undefined, subHooks: [ { + isEditable: false, index: -1, name: 'Custom', value: undefined, subHooks: [ { + isEditable: true, index: 0, name: 'Reducer', value: 'hello', subHooks: [], }, { + isEditable: false, index: 1, name: 'Effect', value: effect, @@ -176,6 +189,7 @@ describe('ReactHooksInspection', () => { ], }, { + isEditable: false, index: 2, name: 'LayoutEffect', value: effect, @@ -184,27 +198,32 @@ describe('ReactHooksInspection', () => { ], }, { + isEditable: false, index: -1, name: 'Baz', value: undefined, subHooks: [ { + isEditable: false, index: 3, name: 'LayoutEffect', value: effect, subHooks: [], }, { + isEditable: false, index: -1, name: 'Custom', subHooks: [ { + isEditable: true, index: 4, name: 'Reducer', subHooks: [], value: 'world', }, { + isEditable: false, index: 5, name: 'Effect', subHooks: [], @@ -227,6 +246,7 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { + isEditable: false, index: 0, name: 'Context', value: 'default', @@ -290,10 +310,13 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { + isEditable: false, index: -1, name: 'Custom', value: __DEV__ ? 'bar:123' : undefined, - subHooks: [{index: 0, name: 'State', subHooks: [], value: 0}], + subHooks: [ + {isEditable: true, index: 0, name: 'State', subHooks: [], value: 0}, + ], }, ]); }); diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index 760ae4ed3f45b..4442e573eb741 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -40,8 +40,8 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root.findByType(Foo)._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {index: 0, name: 'State', value: 'hello', subHooks: []}, - {index: 1, name: 'State', value: 'world', subHooks: []}, + {isEditable: true, index: 0, name: 'State', value: 'hello', subHooks: []}, + {isEditable: true, index: 1, name: 'State', value: 'world', subHooks: []}, ]); let { @@ -55,8 +55,8 @@ describe('ReactHooksInspectionIntegration', () => { tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {index: 0, name: 'State', value: 'Hi', subHooks: []}, - {index: 1, name: 'State', value: 'world', subHooks: []}, + {isEditable: true, index: 0, name: 'State', value: 'Hi', subHooks: []}, + {isEditable: true, index: 1, name: 'State', value: 'world', subHooks: []}, ]); act(() => setStateB('world!')); @@ -65,8 +65,14 @@ describe('ReactHooksInspectionIntegration', () => { tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {index: 0, name: 'State', value: 'Hi', subHooks: []}, - {index: 1, name: 'State', value: 'world!', subHooks: []}, + {isEditable: true, index: 0, name: 'State', value: 'Hi', subHooks: []}, + { + isEditable: true, + index: 1, + name: 'State', + value: 'world!', + subHooks: [], + }, ]); }); @@ -116,19 +122,38 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {index: 0, name: 'State', value: 'a', subHooks: []}, - {index: 1, name: 'Reducer', value: 'b', subHooks: []}, - {index: 2, name: 'Ref', value: 'c', subHooks: []}, - {index: 3, name: 'LayoutEffect', value: effect, subHooks: []}, - {index: 4, name: 'Effect', value: effect, subHooks: []}, + {isEditable: true, index: 0, name: 'State', value: 'a', subHooks: []}, + {isEditable: true, index: 1, name: 'Reducer', value: 'b', subHooks: []}, + {isEditable: false, index: 2, name: 'Ref', value: 'c', subHooks: []}, + { + isEditable: false, + index: 3, + name: 'LayoutEffect', + value: effect, + subHooks: [], + }, { + isEditable: false, + index: 4, + name: 'Effect', + value: effect, + subHooks: [], + }, + { + isEditable: false, index: 5, name: 'ImperativeHandle', value: outsideRef.current, subHooks: [], }, - {index: 6, name: 'Memo', value: 'ab', subHooks: []}, - {index: 7, name: 'Callback', value: updateStates, subHooks: []}, + {isEditable: false, index: 6, name: 'Memo', value: 'ab', subHooks: []}, + { + isEditable: false, + index: 7, + name: 'Callback', + value: updateStates, + subHooks: [], + }, ]); updateStates(); @@ -137,19 +162,38 @@ describe('ReactHooksInspectionIntegration', () => { tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {index: 0, name: 'State', value: 'A', subHooks: []}, - {index: 1, name: 'Reducer', value: 'B', subHooks: []}, - {index: 2, name: 'Ref', value: 'C', subHooks: []}, - {index: 3, name: 'LayoutEffect', value: effect, subHooks: []}, - {index: 4, name: 'Effect', value: effect, subHooks: []}, + {isEditable: true, index: 0, name: 'State', value: 'A', subHooks: []}, + {isEditable: true, index: 1, name: 'Reducer', value: 'B', subHooks: []}, + {isEditable: false, index: 2, name: 'Ref', value: 'C', subHooks: []}, { + isEditable: false, + index: 3, + name: 'LayoutEffect', + value: effect, + subHooks: [], + }, + { + isEditable: false, + index: 4, + name: 'Effect', + value: effect, + subHooks: [], + }, + { + isEditable: false, index: 5, name: 'ImperativeHandle', value: outsideRef.current, subHooks: [], }, - {index: 6, name: 'Memo', value: 'Ab', subHooks: []}, - {index: 7, name: 'Callback', value: updateStates, subHooks: []}, + {isEditable: false, index: 6, name: 'Memo', value: 'Ab', subHooks: []}, + { + isEditable: false, + index: 7, + name: 'Callback', + value: updateStates, + subHooks: [], + }, ]); }); @@ -168,6 +212,7 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { + isEditable: false, index: 0, name: 'Context', value: 'contextual', @@ -188,7 +233,13 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root.findByType(Foo)._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {index: 0, name: 'ImperativeHandle', value: obj, subHooks: []}, + { + isEditable: false, + index: 0, + name: 'ImperativeHandle', + value: obj, + subHooks: [], + }, ]); }); @@ -203,7 +254,7 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root.findByType(InnerFoo)._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {index: 0, name: 'State', value: 'hello', subHooks: []}, + {isEditable: true, index: 0, name: 'State', value: 'hello', subHooks: []}, ]); }); @@ -221,10 +272,19 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { + isEditable: false, index: -1, name: 'Custom', value: undefined, - subHooks: [{index: 0, name: 'State', value: 'hello', subHooks: []}], + subHooks: [ + { + isEditable: true, + index: 0, + name: 'State', + value: 'hello', + subHooks: [], + }, + ], }, ]); }); @@ -252,28 +312,56 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { + isEditable: false, index: -1, name: 'LabeledValue', value: __DEV__ ? 'custom label a' : undefined, - subHooks: [{index: 0, name: 'State', value: 'a', subHooks: []}], + subHooks: [ + { + isEditable: true, + index: 0, + name: 'State', + value: 'a', + subHooks: [], + }, + ], }, { + isEditable: true, index: 1, name: 'State', value: 'b', subHooks: [], }, { + isEditable: false, index: -1, name: 'Anonymous', value: undefined, - subHooks: [{index: 2, name: 'State', value: 'c', subHooks: []}], + subHooks: [ + { + isEditable: true, + index: 2, + name: 'State', + value: 'c', + subHooks: [], + }, + ], }, { + isEditable: false, index: -1, name: 'LabeledValue', value: __DEV__ ? 'custom label d' : undefined, - subHooks: [{index: 3, name: 'State', value: 'd', subHooks: []}], + subHooks: [ + { + isEditable: true, + index: 3, + name: 'State', + value: 'd', + subHooks: [], + }, + ], }, ]); }); @@ -296,15 +384,25 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { + isEditable: false, index: -1, name: 'Outer', value: __DEV__ ? 'outer' : undefined, subHooks: [ { + isEditable: false, index: -1, name: 'Inner', value: __DEV__ ? 'inner' : undefined, - subHooks: [{index: 0, name: 'State', value: 0, subHooks: []}], + subHooks: [ + { + isEditable: true, + index: 0, + name: 'State', + value: 0, + subHooks: [], + }, + ], }, ], }, @@ -333,22 +431,31 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { + isEditable: false, index: -1, name: 'SingleLabelCustom', value: __DEV__ ? 'single one' : undefined, - subHooks: [{index: 0, name: 'State', value: 0, subHooks: []}], + subHooks: [ + {isEditable: true, index: 0, name: 'State', value: 0, subHooks: []}, + ], }, { + isEditable: false, index: -1, name: 'MultiLabelCustom', value: __DEV__ ? ['one', 'two', 'three'] : undefined, - subHooks: [{index: 1, name: 'State', value: 0, subHooks: []}], + subHooks: [ + {isEditable: true, index: 1, name: 'State', value: 0, subHooks: []}, + ], }, { + isEditable: false, index: -1, name: 'SingleLabelCustom', value: __DEV__ ? 'single two' : undefined, - subHooks: [{index: 2, name: 'State', value: 0, subHooks: []}], + subHooks: [ + {isEditable: true, index: 2, name: 'State', value: 0, subHooks: []}, + ], }, ]); }); @@ -378,10 +485,13 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { + isEditable: false, index: -1, name: 'Custom', value: __DEV__ ? 'bar:123' : undefined, - subHooks: [{index: 0, name: 'State', subHooks: [], value: 0}], + subHooks: [ + {isEditable: true, index: 0, name: 'State', subHooks: [], value: 0}, + ], }, ]); }); @@ -415,7 +525,7 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {index: 0, name: 'State', value: 'def', subHooks: []}, + {isEditable: true, index: 0, name: 'State', value: 'def', subHooks: []}, ]); }); diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index 5b45cc5894bf8..5415bba64b9b4 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -369,7 +369,7 @@ if (__DEV__) { return copyWithSetImpl(obj, path, 0, value); }; - // Support DevTools editable hooks state. + // Support DevTools editable values for useState and useReducer. overrideHook = ( fiber: Fiber, index: number, @@ -382,8 +382,11 @@ if (__DEV__) { index--; } if (currentHook !== null) { - let updatedState = copyWithSet(currentHook.memoizedState, path, value); - currentHook.queue.dispatch(updatedState); + flushPassiveEffects(); + const newState = copyWithSet(currentHook.memoizedState, path, value); + currentHook.memoizedState = newState; + currentHook.baseState = newState; + scheduleWork(fiber, Sync); } }; From 0ea3545612e194924bc503b6ccf379e49792e88d Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 23 Feb 2019 09:09:46 -0800 Subject: [PATCH 06/10] Renamed overrideHook -> overrideHookState --- packages/react-reconciler/src/ReactFiberReconciler.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index 5415bba64b9b4..9ffb4fe71567b 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -341,7 +341,7 @@ export function findHostInstanceWithNoPortals( return hostFiber.stateNode; } -let overrideHook = null; +let overrideHookState = null; let overrideProps = null; if (__DEV__) { @@ -370,7 +370,7 @@ if (__DEV__) { }; // Support DevTools editable values for useState and useReducer. - overrideHook = ( + overrideHookState = ( fiber: Fiber, index: number, path: Array, @@ -407,7 +407,7 @@ export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean { return injectInternals({ ...devToolsConfig, - overrideHook, + overrideHookState, overrideProps, currentDispatcherRef: ReactCurrentDispatcher, findHostInstanceByFiber(fiber: Fiber): Instance | TextInstance | null { From 8e3604b9ee23a8fc5c65d10b9a94a072701a6679 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 27 Feb 2019 12:20:10 -0800 Subject: [PATCH 07/10] Update overrideHookState to account for bailout --- packages/react-reconciler/src/ReactFiberReconciler.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index 9ffb4fe71567b..6748e1a6e58a1 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -383,9 +383,18 @@ if (__DEV__) { } if (currentHook !== null) { flushPassiveEffects(); + const newState = copyWithSet(currentHook.memoizedState, path, value); currentHook.memoizedState = newState; currentHook.baseState = newState; + + // We aren't actually adding an update to the queue, + // because there is no update we can add for useReducer hooks that won't trigger an error. + // (There's no appropriate action type for DevTools overrides.) + // As a result though, React will see the scheduled update as a noop and bailout. + // Shallow cloning props works as a workaround for now to bypass the bailout check. + fiber.memoizedProps = {...fiber.memoizedProps}; + scheduleWork(fiber, Sync); } }; From 30bb39bcf84256cdfe6bba6cc49e87d71a6c9e78 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 27 Feb 2019 12:22:12 -0800 Subject: [PATCH 08/10] Renamed inspected hook isEditable to isStateEditable --- .../react-debug-tools/src/ReactDebugHooks.js | 6 +- .../__tests__/ReactHooksInspection-test.js | 50 +++-- .../ReactHooksInspectionIntegration-test.js | 210 ++++++++++++++---- 3 files changed, 193 insertions(+), 73 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 1389ab5bcc2fa..22d0295889427 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -233,7 +233,7 @@ const Dispatcher: DispatcherType = { type HooksNode = { index: number, - isEditable: boolean, + isStateEditable: boolean, name: string, value: mixed, subHooks: Array, @@ -407,7 +407,7 @@ function buildTree(rootStack, readHookLog): HooksTree { let children = []; levelChildren.push({ index: -1, - isEditable: false, + isStateEditable: false, name: parseCustomHookName(stack[j - 1].functionName), value: undefined, subHooks: children, @@ -420,7 +420,7 @@ function buildTree(rootStack, readHookLog): HooksTree { const {primitive} = hook; levelChildren.push({ index: primitive === 'DebugValue' ? -1 : index++, - isEditable: primitive === 'Reducer' || primitive === 'State', + isStateEditable: primitive === 'Reducer' || primitive === 'State', name: primitive, value: hook.value, subHooks: [], diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js index 8121e5e4fb463..4fbb4e2e5de94 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js @@ -28,7 +28,7 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { - isEditable: true, + isStateEditable: true, index: 0, name: 'State', value: 'hello world', @@ -50,13 +50,13 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { - isEditable: false, + isStateEditable: false, index: -1, name: 'Custom', value: __DEV__ ? 'custom hook label' : undefined, subHooks: [ { - isEditable: true, + isStateEditable: true, index: 0, name: 'State', value: 'hello world', @@ -86,20 +86,20 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { - isEditable: false, + isStateEditable: false, index: -1, name: 'Custom', value: undefined, subHooks: [ { - isEditable: true, + isStateEditable: true, index: 0, name: 'State', subHooks: [], value: 'hello', }, { - isEditable: false, + isStateEditable: false, index: 1, name: 'Effect', subHooks: [], @@ -108,20 +108,20 @@ describe('ReactHooksInspection', () => { ], }, { - isEditable: false, + isStateEditable: false, index: -1, name: 'Custom', value: undefined, subHooks: [ { - isEditable: true, + isStateEditable: true, index: 2, name: 'State', value: 'world', subHooks: [], }, { - isEditable: false, + isStateEditable: false, index: 3, name: 'Effect', value: effect, @@ -161,26 +161,26 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { - isEditable: false, + isStateEditable: false, index: -1, name: 'Bar', value: undefined, subHooks: [ { - isEditable: false, + isStateEditable: false, index: -1, name: 'Custom', value: undefined, subHooks: [ { - isEditable: true, + isStateEditable: true, index: 0, name: 'Reducer', value: 'hello', subHooks: [], }, { - isEditable: false, + isStateEditable: false, index: 1, name: 'Effect', value: effect, @@ -189,7 +189,7 @@ describe('ReactHooksInspection', () => { ], }, { - isEditable: false, + isStateEditable: false, index: 2, name: 'LayoutEffect', value: effect, @@ -198,32 +198,32 @@ describe('ReactHooksInspection', () => { ], }, { - isEditable: false, + isStateEditable: false, index: -1, name: 'Baz', value: undefined, subHooks: [ { - isEditable: false, + isStateEditable: false, index: 3, name: 'LayoutEffect', value: effect, subHooks: [], }, { - isEditable: false, + isStateEditable: false, index: -1, name: 'Custom', subHooks: [ { - isEditable: true, + isStateEditable: true, index: 4, name: 'Reducer', subHooks: [], value: 'world', }, { - isEditable: false, + isStateEditable: false, index: 5, name: 'Effect', subHooks: [], @@ -246,7 +246,7 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { - isEditable: false, + isStateEditable: false, index: 0, name: 'Context', value: 'default', @@ -310,12 +310,18 @@ describe('ReactHooksInspection', () => { let tree = ReactDebugTools.inspectHooks(Foo, {}); expect(tree).toEqual([ { - isEditable: false, + isStateEditable: false, index: -1, name: 'Custom', value: __DEV__ ? 'bar:123' : undefined, subHooks: [ - {isEditable: true, index: 0, name: 'State', subHooks: [], value: 0}, + { + isStateEditable: true, + index: 0, + name: 'State', + subHooks: [], + value: 0, + }, ], }, ]); diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index 4442e573eb741..5e00ed91b2448 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -40,8 +40,20 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root.findByType(Foo)._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {isEditable: true, index: 0, name: 'State', value: 'hello', subHooks: []}, - {isEditable: true, index: 1, name: 'State', value: 'world', subHooks: []}, + { + isStateEditable: true, + index: 0, + name: 'State', + value: 'hello', + subHooks: [], + }, + { + isStateEditable: true, + index: 1, + name: 'State', + value: 'world', + subHooks: [], + }, ]); let { @@ -55,8 +67,20 @@ describe('ReactHooksInspectionIntegration', () => { tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {isEditable: true, index: 0, name: 'State', value: 'Hi', subHooks: []}, - {isEditable: true, index: 1, name: 'State', value: 'world', subHooks: []}, + { + isStateEditable: true, + index: 0, + name: 'State', + value: 'Hi', + subHooks: [], + }, + { + isStateEditable: true, + index: 1, + name: 'State', + value: 'world', + subHooks: [], + }, ]); act(() => setStateB('world!')); @@ -65,9 +89,15 @@ describe('ReactHooksInspectionIntegration', () => { tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {isEditable: true, index: 0, name: 'State', value: 'Hi', subHooks: []}, { - isEditable: true, + isStateEditable: true, + index: 0, + name: 'State', + value: 'Hi', + subHooks: [], + }, + { + isStateEditable: true, index: 1, name: 'State', value: 'world!', @@ -122,33 +152,51 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {isEditable: true, index: 0, name: 'State', value: 'a', subHooks: []}, - {isEditable: true, index: 1, name: 'Reducer', value: 'b', subHooks: []}, - {isEditable: false, index: 2, name: 'Ref', value: 'c', subHooks: []}, { - isEditable: false, + isStateEditable: true, + index: 0, + name: 'State', + value: 'a', + subHooks: [], + }, + { + isStateEditable: true, + index: 1, + name: 'Reducer', + value: 'b', + subHooks: [], + }, + {isStateEditable: false, index: 2, name: 'Ref', value: 'c', subHooks: []}, + { + isStateEditable: false, index: 3, name: 'LayoutEffect', value: effect, subHooks: [], }, { - isEditable: false, + isStateEditable: false, index: 4, name: 'Effect', value: effect, subHooks: [], }, { - isEditable: false, + isStateEditable: false, index: 5, name: 'ImperativeHandle', value: outsideRef.current, subHooks: [], }, - {isEditable: false, index: 6, name: 'Memo', value: 'ab', subHooks: []}, { - isEditable: false, + isStateEditable: false, + index: 6, + name: 'Memo', + value: 'ab', + subHooks: [], + }, + { + isStateEditable: false, index: 7, name: 'Callback', value: updateStates, @@ -162,33 +210,51 @@ describe('ReactHooksInspectionIntegration', () => { tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {isEditable: true, index: 0, name: 'State', value: 'A', subHooks: []}, - {isEditable: true, index: 1, name: 'Reducer', value: 'B', subHooks: []}, - {isEditable: false, index: 2, name: 'Ref', value: 'C', subHooks: []}, { - isEditable: false, + isStateEditable: true, + index: 0, + name: 'State', + value: 'A', + subHooks: [], + }, + { + isStateEditable: true, + index: 1, + name: 'Reducer', + value: 'B', + subHooks: [], + }, + {isStateEditable: false, index: 2, name: 'Ref', value: 'C', subHooks: []}, + { + isStateEditable: false, index: 3, name: 'LayoutEffect', value: effect, subHooks: [], }, { - isEditable: false, + isStateEditable: false, index: 4, name: 'Effect', value: effect, subHooks: [], }, { - isEditable: false, + isStateEditable: false, index: 5, name: 'ImperativeHandle', value: outsideRef.current, subHooks: [], }, - {isEditable: false, index: 6, name: 'Memo', value: 'Ab', subHooks: []}, { - isEditable: false, + isStateEditable: false, + index: 6, + name: 'Memo', + value: 'Ab', + subHooks: [], + }, + { + isStateEditable: false, index: 7, name: 'Callback', value: updateStates, @@ -212,7 +278,7 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { - isEditable: false, + isStateEditable: false, index: 0, name: 'Context', value: 'contextual', @@ -234,7 +300,7 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { - isEditable: false, + isStateEditable: false, index: 0, name: 'ImperativeHandle', value: obj, @@ -254,7 +320,13 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root.findByType(InnerFoo)._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {isEditable: true, index: 0, name: 'State', value: 'hello', subHooks: []}, + { + isStateEditable: true, + index: 0, + name: 'State', + value: 'hello', + subHooks: [], + }, ]); }); @@ -272,13 +344,13 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { - isEditable: false, + isStateEditable: false, index: -1, name: 'Custom', value: undefined, subHooks: [ { - isEditable: true, + isStateEditable: true, index: 0, name: 'State', value: 'hello', @@ -312,13 +384,13 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { - isEditable: false, + isStateEditable: false, index: -1, name: 'LabeledValue', value: __DEV__ ? 'custom label a' : undefined, subHooks: [ { - isEditable: true, + isStateEditable: true, index: 0, name: 'State', value: 'a', @@ -327,20 +399,20 @@ describe('ReactHooksInspectionIntegration', () => { ], }, { - isEditable: true, + isStateEditable: true, index: 1, name: 'State', value: 'b', subHooks: [], }, { - isEditable: false, + isStateEditable: false, index: -1, name: 'Anonymous', value: undefined, subHooks: [ { - isEditable: true, + isStateEditable: true, index: 2, name: 'State', value: 'c', @@ -349,13 +421,13 @@ describe('ReactHooksInspectionIntegration', () => { ], }, { - isEditable: false, + isStateEditable: false, index: -1, name: 'LabeledValue', value: __DEV__ ? 'custom label d' : undefined, subHooks: [ { - isEditable: true, + isStateEditable: true, index: 3, name: 'State', value: 'd', @@ -384,19 +456,19 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { - isEditable: false, + isStateEditable: false, index: -1, name: 'Outer', value: __DEV__ ? 'outer' : undefined, subHooks: [ { - isEditable: false, + isStateEditable: false, index: -1, name: 'Inner', value: __DEV__ ? 'inner' : undefined, subHooks: [ { - isEditable: true, + isStateEditable: true, index: 0, name: 'State', value: 0, @@ -431,30 +503,48 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { - isEditable: false, + isStateEditable: false, index: -1, name: 'SingleLabelCustom', value: __DEV__ ? 'single one' : undefined, subHooks: [ - {isEditable: true, index: 0, name: 'State', value: 0, subHooks: []}, + { + isStateEditable: true, + index: 0, + name: 'State', + value: 0, + subHooks: [], + }, ], }, { - isEditable: false, + isStateEditable: false, index: -1, name: 'MultiLabelCustom', value: __DEV__ ? ['one', 'two', 'three'] : undefined, subHooks: [ - {isEditable: true, index: 1, name: 'State', value: 0, subHooks: []}, + { + isStateEditable: true, + index: 1, + name: 'State', + value: 0, + subHooks: [], + }, ], }, { - isEditable: false, + isStateEditable: false, index: -1, name: 'SingleLabelCustom', value: __DEV__ ? 'single two' : undefined, subHooks: [ - {isEditable: true, index: 2, name: 'State', value: 0, subHooks: []}, + { + isStateEditable: true, + index: 2, + name: 'State', + value: 0, + subHooks: [], + }, ], }, ]); @@ -485,12 +575,18 @@ describe('ReactHooksInspectionIntegration', () => { let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ { - isEditable: false, + isStateEditable: false, index: -1, name: 'Custom', value: __DEV__ ? 'bar:123' : undefined, subHooks: [ - {isEditable: true, index: 0, name: 'State', subHooks: [], value: 0}, + { + isStateEditable: true, + index: 0, + name: 'State', + subHooks: [], + value: 0, + }, ], }, ]); @@ -525,7 +621,13 @@ describe('ReactHooksInspectionIntegration', () => { let childFiber = renderer.root._currentFiber(); let tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {isEditable: true, index: 0, name: 'State', value: 'def', subHooks: []}, + { + isStateEditable: true, + index: 0, + name: 'State', + value: 'def', + subHooks: [], + }, ]); }); @@ -597,8 +699,20 @@ describe('ReactHooksInspectionIntegration', () => { const childFiber = renderer.root._currentFiber(); const tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree).toEqual([ - {name: 'Context', value: 1, subHooks: []}, - {name: 'State', value: {count: 2}, subHooks: []}, + { + isStateEditable: false, + index: 0, + name: 'Context', + value: 1, + subHooks: [], + }, + { + isStateEditable: true, + index: 1, + name: 'State', + value: {count: 2}, + subHooks: [], + }, ]); }); }); From cdd9ba46d3eedd94bdb50f0e80a71673336b7e0b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 28 Feb 2019 13:20:43 -0800 Subject: [PATCH 09/10] Renamed debug hook 'index' attr to 'id'. Updated to account for Context not being in stateful list. Added inline comments. --- .../react-debug-tools/src/ReactDebugHooks.js | 21 +++- .../__tests__/ReactHooksInspection-test.js | 44 ++++----- .../ReactHooksInspectionIntegration-test.js | 96 +++++++++---------- .../src/ReactFiberReconciler.js | 8 +- 4 files changed, 91 insertions(+), 78 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 22d0295889427..12e3021d8afdf 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -232,7 +232,7 @@ const Dispatcher: DispatcherType = { // Inspect type HooksNode = { - index: number, + id: number | null, isStateEditable: boolean, name: string, value: mixed, @@ -375,7 +375,7 @@ function buildTree(rootStack, readHookLog): HooksTree { let rootChildren = []; let prevStack = null; let levelChildren = rootChildren; - let index = 0; + let nativeHookID = 0; let stackOfChildren = []; for (let i = 0; i < readHookLog.length; i++) { let hook = readHookLog[i]; @@ -406,7 +406,7 @@ function buildTree(rootStack, readHookLog): HooksTree { for (let j = stack.length - commonSteps - 1; j >= 1; j--) { let children = []; levelChildren.push({ - index: -1, + id: null, isStateEditable: false, name: parseCustomHookName(stack[j - 1].functionName), value: undefined, @@ -418,9 +418,20 @@ function buildTree(rootStack, readHookLog): HooksTree { prevStack = stack; } const {primitive} = hook; + + // For now, the "id" of stateful hooks is just the stateful hook index. + // Custom hooks have no ids, nor do non-stateful native hooks (e.g. Context, DebugValue). + const id = + primitive === 'Context' || primitive === 'DebugValue' + ? null + : nativeHookID++; + + // For the time being, only State and Reducer hooks support runtime overrides. + const isStateEditable = primitive === 'Reducer' || primitive === 'State'; + levelChildren.push({ - index: primitive === 'DebugValue' ? -1 : index++, - isStateEditable: primitive === 'Reducer' || primitive === 'State', + id, + isStateEditable, name: primitive, value: hook.value, subHooks: [], diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js index 4fbb4e2e5de94..f5b57a531abaa 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js @@ -29,7 +29,7 @@ describe('ReactHooksInspection', () => { expect(tree).toEqual([ { isStateEditable: true, - index: 0, + id: 0, name: 'State', value: 'hello world', subHooks: [], @@ -51,13 +51,13 @@ describe('ReactHooksInspection', () => { expect(tree).toEqual([ { isStateEditable: false, - index: -1, + id: null, name: 'Custom', value: __DEV__ ? 'custom hook label' : undefined, subHooks: [ { isStateEditable: true, - index: 0, + id: 0, name: 'State', value: 'hello world', subHooks: [], @@ -87,20 +87,20 @@ describe('ReactHooksInspection', () => { expect(tree).toEqual([ { isStateEditable: false, - index: -1, + id: null, name: 'Custom', value: undefined, subHooks: [ { isStateEditable: true, - index: 0, + id: 0, name: 'State', subHooks: [], value: 'hello', }, { isStateEditable: false, - index: 1, + id: 1, name: 'Effect', subHooks: [], value: effect, @@ -109,20 +109,20 @@ describe('ReactHooksInspection', () => { }, { isStateEditable: false, - index: -1, + id: null, name: 'Custom', value: undefined, subHooks: [ { isStateEditable: true, - index: 2, + id: 2, name: 'State', value: 'world', subHooks: [], }, { isStateEditable: false, - index: 3, + id: 3, name: 'Effect', value: effect, subHooks: [], @@ -162,26 +162,26 @@ describe('ReactHooksInspection', () => { expect(tree).toEqual([ { isStateEditable: false, - index: -1, + id: null, name: 'Bar', value: undefined, subHooks: [ { isStateEditable: false, - index: -1, + id: null, name: 'Custom', value: undefined, subHooks: [ { isStateEditable: true, - index: 0, + id: 0, name: 'Reducer', value: 'hello', subHooks: [], }, { isStateEditable: false, - index: 1, + id: 1, name: 'Effect', value: effect, subHooks: [], @@ -190,7 +190,7 @@ describe('ReactHooksInspection', () => { }, { isStateEditable: false, - index: 2, + id: 2, name: 'LayoutEffect', value: effect, subHooks: [], @@ -199,32 +199,32 @@ describe('ReactHooksInspection', () => { }, { isStateEditable: false, - index: -1, + id: null, name: 'Baz', value: undefined, subHooks: [ { isStateEditable: false, - index: 3, + id: 3, name: 'LayoutEffect', value: effect, subHooks: [], }, { isStateEditable: false, - index: -1, + id: null, name: 'Custom', subHooks: [ { isStateEditable: true, - index: 4, + id: 4, name: 'Reducer', subHooks: [], value: 'world', }, { isStateEditable: false, - index: 5, + id: 5, name: 'Effect', subHooks: [], value: effect, @@ -247,7 +247,7 @@ describe('ReactHooksInspection', () => { expect(tree).toEqual([ { isStateEditable: false, - index: 0, + id: null, name: 'Context', value: 'default', subHooks: [], @@ -311,13 +311,13 @@ describe('ReactHooksInspection', () => { expect(tree).toEqual([ { isStateEditable: false, - index: -1, + id: null, name: 'Custom', value: __DEV__ ? 'bar:123' : undefined, subHooks: [ { isStateEditable: true, - index: 0, + id: 0, name: 'State', subHooks: [], value: 0, diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index 5e00ed91b2448..c12270626b0a4 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -42,14 +42,14 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: true, - index: 0, + id: 0, name: 'State', value: 'hello', subHooks: [], }, { isStateEditable: true, - index: 1, + id: 1, name: 'State', value: 'world', subHooks: [], @@ -69,14 +69,14 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: true, - index: 0, + id: 0, name: 'State', value: 'Hi', subHooks: [], }, { isStateEditable: true, - index: 1, + id: 1, name: 'State', value: 'world', subHooks: [], @@ -91,14 +91,14 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: true, - index: 0, + id: 0, name: 'State', value: 'Hi', subHooks: [], }, { isStateEditable: true, - index: 1, + id: 1, name: 'State', value: 'world!', subHooks: [], @@ -154,50 +154,50 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: true, - index: 0, + id: 0, name: 'State', value: 'a', subHooks: [], }, { isStateEditable: true, - index: 1, + id: 1, name: 'Reducer', value: 'b', subHooks: [], }, - {isStateEditable: false, index: 2, name: 'Ref', value: 'c', subHooks: []}, + {isStateEditable: false, id: 2, name: 'Ref', value: 'c', subHooks: []}, { isStateEditable: false, - index: 3, + id: 3, name: 'LayoutEffect', value: effect, subHooks: [], }, { isStateEditable: false, - index: 4, + id: 4, name: 'Effect', value: effect, subHooks: [], }, { isStateEditable: false, - index: 5, + id: 5, name: 'ImperativeHandle', value: outsideRef.current, subHooks: [], }, { isStateEditable: false, - index: 6, + id: 6, name: 'Memo', value: 'ab', subHooks: [], }, { isStateEditable: false, - index: 7, + id: 7, name: 'Callback', value: updateStates, subHooks: [], @@ -212,50 +212,50 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: true, - index: 0, + id: 0, name: 'State', value: 'A', subHooks: [], }, { isStateEditable: true, - index: 1, + id: 1, name: 'Reducer', value: 'B', subHooks: [], }, - {isStateEditable: false, index: 2, name: 'Ref', value: 'C', subHooks: []}, + {isStateEditable: false, id: 2, name: 'Ref', value: 'C', subHooks: []}, { isStateEditable: false, - index: 3, + id: 3, name: 'LayoutEffect', value: effect, subHooks: [], }, { isStateEditable: false, - index: 4, + id: 4, name: 'Effect', value: effect, subHooks: [], }, { isStateEditable: false, - index: 5, + id: 5, name: 'ImperativeHandle', value: outsideRef.current, subHooks: [], }, { isStateEditable: false, - index: 6, + id: 6, name: 'Memo', value: 'Ab', subHooks: [], }, { isStateEditable: false, - index: 7, + id: 7, name: 'Callback', value: updateStates, subHooks: [], @@ -279,7 +279,7 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: false, - index: 0, + id: null, name: 'Context', value: 'contextual', subHooks: [], @@ -301,7 +301,7 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: false, - index: 0, + id: 0, name: 'ImperativeHandle', value: obj, subHooks: [], @@ -322,7 +322,7 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: true, - index: 0, + id: 0, name: 'State', value: 'hello', subHooks: [], @@ -345,13 +345,13 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: false, - index: -1, + id: null, name: 'Custom', value: undefined, subHooks: [ { isStateEditable: true, - index: 0, + id: 0, name: 'State', value: 'hello', subHooks: [], @@ -385,13 +385,13 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: false, - index: -1, + id: null, name: 'LabeledValue', value: __DEV__ ? 'custom label a' : undefined, subHooks: [ { isStateEditable: true, - index: 0, + id: 0, name: 'State', value: 'a', subHooks: [], @@ -400,20 +400,20 @@ describe('ReactHooksInspectionIntegration', () => { }, { isStateEditable: true, - index: 1, + id: 1, name: 'State', value: 'b', subHooks: [], }, { isStateEditable: false, - index: -1, + id: null, name: 'Anonymous', value: undefined, subHooks: [ { isStateEditable: true, - index: 2, + id: 2, name: 'State', value: 'c', subHooks: [], @@ -422,13 +422,13 @@ describe('ReactHooksInspectionIntegration', () => { }, { isStateEditable: false, - index: -1, + id: null, name: 'LabeledValue', value: __DEV__ ? 'custom label d' : undefined, subHooks: [ { isStateEditable: true, - index: 3, + id: 3, name: 'State', value: 'd', subHooks: [], @@ -457,19 +457,19 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: false, - index: -1, + id: null, name: 'Outer', value: __DEV__ ? 'outer' : undefined, subHooks: [ { isStateEditable: false, - index: -1, + id: null, name: 'Inner', value: __DEV__ ? 'inner' : undefined, subHooks: [ { isStateEditable: true, - index: 0, + id: 0, name: 'State', value: 0, subHooks: [], @@ -504,13 +504,13 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: false, - index: -1, + id: null, name: 'SingleLabelCustom', value: __DEV__ ? 'single one' : undefined, subHooks: [ { isStateEditable: true, - index: 0, + id: 0, name: 'State', value: 0, subHooks: [], @@ -519,13 +519,13 @@ describe('ReactHooksInspectionIntegration', () => { }, { isStateEditable: false, - index: -1, + id: null, name: 'MultiLabelCustom', value: __DEV__ ? ['one', 'two', 'three'] : undefined, subHooks: [ { isStateEditable: true, - index: 1, + id: 1, name: 'State', value: 0, subHooks: [], @@ -534,13 +534,13 @@ describe('ReactHooksInspectionIntegration', () => { }, { isStateEditable: false, - index: -1, + id: null, name: 'SingleLabelCustom', value: __DEV__ ? 'single two' : undefined, subHooks: [ { isStateEditable: true, - index: 2, + id: 2, name: 'State', value: 0, subHooks: [], @@ -576,13 +576,13 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: false, - index: -1, + id: null, name: 'Custom', value: __DEV__ ? 'bar:123' : undefined, subHooks: [ { isStateEditable: true, - index: 0, + id: 0, name: 'State', subHooks: [], value: 0, @@ -623,7 +623,7 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: true, - index: 0, + id: 0, name: 'State', value: 'def', subHooks: [], @@ -701,14 +701,14 @@ describe('ReactHooksInspectionIntegration', () => { expect(tree).toEqual([ { isStateEditable: false, - index: 0, + id: null, name: 'Context', value: 1, subHooks: [], }, { isStateEditable: true, - index: 1, + id: 0, name: 'State', value: {count: 2}, subHooks: [], diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index 6748e1a6e58a1..01e303c74bc42 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -372,14 +372,16 @@ if (__DEV__) { // Support DevTools editable values for useState and useReducer. overrideHookState = ( fiber: Fiber, - index: number, + id: number, path: Array, value: any, ) => { + // For now, the "id" of stateful hooks is just the stateful hook index. + // This may change in the future with e.g. nested hooks. let currentHook = fiber.memoizedState; - while (currentHook !== null && index > 0) { + while (currentHook !== null && id > 0) { currentHook = currentHook.next; - index--; + id--; } if (currentHook !== null) { flushPassiveEffects(); From d450ead132f9caec0407c303c8f71b3d36c518e8 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 28 Feb 2019 14:24:11 -0800 Subject: [PATCH 10/10] Added an integration test for React DevTools + react-debug-tools + editable hooks --- .../ReactDevToolsHooksIntegration-test.js | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js diff --git a/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js new file mode 100644 index 0000000000000..113524f832af5 --- /dev/null +++ b/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js @@ -0,0 +1,176 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + * @jest-environment node + */ + +'use strict'; + +describe('React hooks DevTools integration', () => { + let React; + let ReactDebugTools; + let ReactTestRenderer; + let act; + let overrideHookState; + + beforeEach(() => { + global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { + inject: injected => { + overrideHookState = injected.overrideHookState; + }, + supportsFiber: true, + onCommitFiberRoot: () => {}, + onCommitFiberUnmount: () => {}, + }; + + jest.resetModules(); + + React = require('react'); + ReactDebugTools = require('react-debug-tools'); + ReactTestRenderer = require('react-test-renderer'); + + act = ReactTestRenderer.act; + }); + + it('should support editing useState hooks', () => { + let setCountFn; + + function MyComponent() { + const [count, setCount] = React.useState(0); + setCountFn = setCount; + return
count:{count}
; + } + + const renderer = ReactTestRenderer.create(); + expect(renderer.toJSON()).toEqual({ + type: 'div', + props: {}, + children: ['count:', '0'], + }); + + const fiber = renderer.root.findByType(MyComponent)._currentFiber(); + const tree = ReactDebugTools.inspectHooksOfFiber(fiber); + const stateHook = tree[0]; + expect(stateHook.isStateEditable).toBe(true); + + if (__DEV__) { + overrideHookState(fiber, stateHook.id, [], 10); + expect(renderer.toJSON()).toEqual({ + type: 'div', + props: {}, + children: ['count:', '10'], + }); + + act(() => setCountFn(count => count + 1)); + expect(renderer.toJSON()).toEqual({ + type: 'div', + props: {}, + children: ['count:', '11'], + }); + } + }); + + it('should support editable useReducer hooks', () => { + const initialData = {foo: 'abc', bar: 123}; + + function reducer(state, action) { + switch (action.type) { + case 'swap': + return {foo: state.bar, bar: state.foo}; + default: + throw new Error(); + } + } + + let dispatchFn; + function MyComponent() { + const [state, dispatch] = React.useReducer(reducer, initialData); + dispatchFn = dispatch; + return ( +
+ foo:{state.foo}, bar:{state.bar} +
+ ); + } + + const renderer = ReactTestRenderer.create(); + expect(renderer.toJSON()).toEqual({ + type: 'div', + props: {}, + children: ['foo:', 'abc', ', bar:', '123'], + }); + + const fiber = renderer.root.findByType(MyComponent)._currentFiber(); + const tree = ReactDebugTools.inspectHooksOfFiber(fiber); + const reducerHook = tree[0]; + expect(reducerHook.isStateEditable).toBe(true); + + if (__DEV__) { + overrideHookState(fiber, reducerHook.id, ['foo'], 'def'); + expect(renderer.toJSON()).toEqual({ + type: 'div', + props: {}, + children: ['foo:', 'def', ', bar:', '123'], + }); + + act(() => dispatchFn({type: 'swap'})); + expect(renderer.toJSON()).toEqual({ + type: 'div', + props: {}, + children: ['foo:', '123', ', bar:', 'def'], + }); + } + }); + + // This test case is based on an open source bug report: + // facebookincubator/redux-react-hook/issues/34#issuecomment-466693787 + it('should handle interleaved stateful hooks (e.g. useState) and non-stateful hooks (e.g. useContext)', () => { + const MyContext = React.createContext(1); + + let setStateFn; + function useCustomHook() { + const context = React.useContext(MyContext); + const [state, setState] = React.useState({count: context}); + React.useDebugValue(state.count); + setStateFn = setState; + return state.count; + } + + function MyComponent() { + const count = useCustomHook(); + return
count:{count}
; + } + + const renderer = ReactTestRenderer.create(); + expect(renderer.toJSON()).toEqual({ + type: 'div', + props: {}, + children: ['count:', '1'], + }); + + const fiber = renderer.root.findByType(MyComponent)._currentFiber(); + const tree = ReactDebugTools.inspectHooksOfFiber(fiber); + const stateHook = tree[0].subHooks[1]; + expect(stateHook.isStateEditable).toBe(true); + + if (__DEV__) { + overrideHookState(fiber, stateHook.id, ['count'], 10); + expect(renderer.toJSON()).toEqual({ + type: 'div', + props: {}, + children: ['count:', '10'], + }); + + act(() => setStateFn(state => ({count: state.count + 1}))); + expect(renderer.toJSON()).toEqual({ + type: 'div', + props: {}, + children: ['count:', '11'], + }); + } + }); +});