Skip to content

Commit 3e20cf6

Browse files
author
Brian Vaughn
committed
Support custom values for custom hooks
1 parent f13b4be commit 3e20cf6

File tree

7 files changed

+174
-2
lines changed

7 files changed

+174
-2
lines changed

packages/react-debug-tools/src/ReactDebugHooks.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
5555
Dispatcher.useLayoutEffect(() => {});
5656
Dispatcher.useEffect(() => {});
5757
Dispatcher.useImperativeHandle(undefined, () => null);
58+
Dispatcher.useDebugValueLabel(null);
5859
Dispatcher.useCallback(() => {});
5960
Dispatcher.useMemo(() => null);
6061
} finally {
@@ -180,6 +181,14 @@ function useImperativeHandle<T>(
180181
});
181182
}
182183

184+
function useDebugValueLabel(valueLabel: any) {
185+
hookLog.push({
186+
primitive: 'DebugValueLabel',
187+
stackError: new Error(),
188+
value: valueLabel,
189+
});
190+
}
191+
183192
function useCallback<T>(callback: T, inputs: Array<mixed> | void | null): T {
184193
let hook = nextHook();
185194
hookLog.push({
@@ -205,7 +214,12 @@ const Dispatcher = {
205214
useCallback,
206215
useContext,
207216
useEffect,
217+
<<<<<<< HEAD
208218
useImperativeHandle,
219+
=======
220+
useImperativeMethods,
221+
useDebugValueLabel,
222+
>>>>>>> Support custom values for custom hooks
209223
useLayoutEffect,
210224
useMemo,
211225
useReducer,
@@ -388,7 +402,7 @@ function buildTree(rootStack, readHookLog): HooksTree {
388402
let children = [];
389403
levelChildren.push({
390404
name: parseCustomHookName(stack[j - 1].functionName),
391-
value: undefined, // TODO: Support custom inspectable values.
405+
value: undefined,
392406
subHooks: children,
393407
});
394408
stackOfChildren.push(levelChildren);
@@ -402,9 +416,31 @@ function buildTree(rootStack, readHookLog): HooksTree {
402416
subHooks: [],
403417
});
404418
}
419+
420+
// Associate custom hook values (useInpect() hook entries) with the correct hooks
421+
rootChildren.forEach(hooksNode => rollupDebugValueLabels(hooksNode));
422+
405423
return rootChildren;
406424
}
407425

426+
function rollupDebugValueLabels(hooksNode: HooksNode): void {
427+
let useInpectHooksNodes: Array<HooksNode> = [];
428+
hooksNode.subHooks = hooksNode.subHooks.filter(subHooksNode => {
429+
if (subHooksNode.name === 'DebugValueLabel') {
430+
useInpectHooksNodes.push(subHooksNode);
431+
return false;
432+
} else {
433+
rollupDebugValueLabels(subHooksNode);
434+
return true;
435+
}
436+
});
437+
if (useInpectHooksNodes.length === 1) {
438+
hooksNode.value = useInpectHooksNodes[0].value;
439+
} else if (useInpectHooksNodes.length > 1) {
440+
hooksNode.value = useInpectHooksNodes.map(({value}) => value);
441+
}
442+
}
443+
408444
export function inspectHooks<Props>(
409445
currentDispatcher: CurrentDispatcherRef,
410446
renderFunction: Props => React$Node,

packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.internal.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ describe('ReactHooksInspection', () => {
4646
it('should inspect a simple custom hook', () => {
4747
function useCustom(value) {
4848
let [state] = React.useState(value);
49+
React.useDebugValueLabel('custom hook label');
4950
return state;
5051
}
5152
function Foo(props) {
@@ -56,7 +57,7 @@ describe('ReactHooksInspection', () => {
5657
expect(tree).toEqual([
5758
{
5859
name: 'Custom',
59-
value: undefined,
60+
value: 'custom hook label',
6061
subHooks: [
6162
{
6263
name: 'State',

packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.internal.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,122 @@ describe('ReactHooksInspectionIntergration', () => {
235235
]);
236236
});
237237

238+
describe('useDebugValueLabel', () => {
239+
it('should support inspectable values for multiple custom hooks', () => {
240+
function useLabeledValue(label) {
241+
let [value] = React.useState(label);
242+
React.useDebugValueLabel(`custom label ${label}`);
243+
return value;
244+
}
245+
function useAnonymous(label) {
246+
let [value] = React.useState(label);
247+
return value;
248+
}
249+
function Example(props) {
250+
useLabeledValue('a');
251+
React.useState('b');
252+
useAnonymous('c');
253+
useLabeledValue('d');
254+
return null;
255+
}
256+
let renderer = ReactTestRenderer.create(<Example />);
257+
let childFiber = renderer.root.findByType(Example)._currentFiber();
258+
let tree = ReactDebugTools.inspectHooksOfFiber(
259+
currentDispatcher,
260+
childFiber,
261+
);
262+
expect(tree).toEqual([
263+
{
264+
name: 'LabeledValue',
265+
value: 'custom label a',
266+
subHooks: [{name: 'State', value: 'a', subHooks: []}],
267+
},
268+
{
269+
name: 'State',
270+
value: 'b',
271+
subHooks: [],
272+
},
273+
{
274+
name: 'Anonymous',
275+
value: undefined,
276+
subHooks: [{name: 'State', value: 'c', subHooks: []}],
277+
},
278+
{
279+
name: 'LabeledValue',
280+
value: 'custom label d',
281+
subHooks: [{name: 'State', value: 'd', subHooks: []}],
282+
},
283+
]);
284+
});
285+
286+
it('should support inspectable values for nested custom hooks', () => {
287+
function useInner() {
288+
React.useDebugValueLabel('inner');
289+
}
290+
function useOuter() {
291+
React.useDebugValueLabel('outer');
292+
useInner();
293+
}
294+
function Example(props) {
295+
useOuter();
296+
return null;
297+
}
298+
let renderer = ReactTestRenderer.create(<Example />);
299+
let childFiber = renderer.root.findByType(Example)._currentFiber();
300+
let tree = ReactDebugTools.inspectHooksOfFiber(
301+
currentDispatcher,
302+
childFiber,
303+
);
304+
expect(tree).toEqual([
305+
{
306+
name: 'Outer',
307+
value: 'outer',
308+
subHooks: [{name: 'Inner', value: 'inner', subHooks: []}],
309+
},
310+
]);
311+
});
312+
313+
it('should support multiple inspectable values per custom hooks', () => {
314+
function useMultiLabelCustom() {
315+
React.useDebugValueLabel('one');
316+
React.useDebugValueLabel('two');
317+
React.useDebugValueLabel('three');
318+
}
319+
function useSingleLabelCustom(value) {
320+
React.useDebugValueLabel(`single ${value}`);
321+
}
322+
function Example(props) {
323+
useSingleLabelCustom('one');
324+
useMultiLabelCustom();
325+
useSingleLabelCustom('two');
326+
return null;
327+
}
328+
let renderer = ReactTestRenderer.create(<Example />);
329+
let childFiber = renderer.root.findByType(Example)._currentFiber();
330+
let tree = ReactDebugTools.inspectHooksOfFiber(
331+
currentDispatcher,
332+
childFiber,
333+
);
334+
expect(tree).toEqual([
335+
{
336+
name: 'SingleLabelCustom',
337+
value: 'single one',
338+
subHooks: [],
339+
},
340+
{
341+
name: 'MultiLabelCustom',
342+
value: ['one', 'two', 'three'],
343+
subHooks: [],
344+
},
345+
{
346+
name: 'SingleLabelCustom',
347+
value: 'single two',
348+
subHooks: [],
349+
},
350+
]);
351+
});
352+
});
353+
238354
it('should support defaultProps and lazy', async () => {
239355
let Suspense = React.Suspense;
240356

packages/react-reconciler/src/ReactFiberDispatcher.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
useContext,
1414
useEffect,
1515
useImperativeHandle,
16+
useDebugValueLabel,
1617
useLayoutEffect,
1718
useMemo,
1819
useReducer,
@@ -26,6 +27,7 @@ export const Dispatcher = {
2627
useContext,
2728
useEffect,
2829
useImperativeHandle,
30+
useDebugValueLabel,
2931
useLayoutEffect,
3032
useMemo,
3133
useReducer,

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,12 @@ export function useImperativeHandle<T>(
588588
}, nextInputs);
589589
}
590590

591+
export function useDebugValueLabel(valueLabel: string): void {
592+
// This hook is normally a no-op.
593+
// The react-debug-hooks package injects its own implementation
594+
// so that e.g. DevTools can display customhook values.
595+
}
596+
591597
export function useCallback<T>(
592598
callback: T,
593599
inputs: Array<mixed> | void | null,

packages/react/src/React.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
useContext,
3434
useEffect,
3535
useImperativeHandle,
36+
useDebugValueLabel,
3637
useLayoutEffect,
3738
useMemo,
3839
useReducer,
@@ -98,7 +99,12 @@ if (enableHooks) {
9899
React.useCallback = useCallback;
99100
React.useContext = useContext;
100101
React.useEffect = useEffect;
102+
<<<<<<< HEAD
101103
React.useImperativeHandle = useImperativeHandle;
104+
=======
105+
React.useImperativeMethods = useImperativeMethods;
106+
React.useDebugValueLabel = useDebugValueLabel;
107+
>>>>>>> Support custom values for custom hooks
102108
React.useLayoutEffect = useLayoutEffect;
103109
React.useMemo = useMemo;
104110
React.useReducer = useReducer;

packages/react/src/ReactHooks.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,8 @@ export function useImperativeHandle<T>(
110110
const dispatcher = resolveDispatcher();
111111
return dispatcher.useImperativeHandle(ref, create, inputs);
112112
}
113+
114+
export function useDebugValueLabel(valueLabel: string) {
115+
const dispatcher = resolveDispatcher();
116+
return dispatcher.useDebugValueLabel(valueLabel);
117+
}

0 commit comments

Comments
 (0)