mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Added simple hooks support (same as in legacy DevTools for now)
I had to add a couple of comments because Flow was being a mysterious little shit and I got tired of trying to work around it.
This commit is contained in:
@@ -88,8 +88,8 @@ class ModernContextType extends Component<any> {
|
||||
}
|
||||
|
||||
function FunctionalContextConsumer() {
|
||||
const string = useContext(StringContext);
|
||||
return string;
|
||||
useContext(StringContext);
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function Contexts() {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// @flow
|
||||
|
||||
import React, {
|
||||
forwardRef,
|
||||
Fragment,
|
||||
memo,
|
||||
useCallback,
|
||||
// $FlowFixMe Flow doesn't yet know about this hook
|
||||
useDebugValue,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
function useNestedInnerHook() {
|
||||
return useState(123);
|
||||
}
|
||||
function useNestedOuterHook() {
|
||||
return useNestedInnerHook();
|
||||
}
|
||||
|
||||
function FunctionWithHooks(props: any, ref: React$Ref<any>) {
|
||||
const [count, updateCount] = useState(0);
|
||||
|
||||
// Custom hook with a custom debug label
|
||||
const debouncedCount = useDebounce(count, 1000);
|
||||
|
||||
const onClick = useCallback(
|
||||
function onClick() {
|
||||
updateCount(count + 1);
|
||||
},
|
||||
[count]
|
||||
);
|
||||
|
||||
// Tests nested custom hooks
|
||||
useNestedOuterHook();
|
||||
|
||||
return <button onClick={onClick}>Count: {debouncedCount}</button>;
|
||||
}
|
||||
const MemoWithHooks = memo(FunctionWithHooks);
|
||||
const ForwardRefWithHooks = forwardRef(FunctionWithHooks);
|
||||
|
||||
export default function CustomHooks() {
|
||||
return (
|
||||
<Fragment>
|
||||
<FunctionWithHooks />
|
||||
<MemoWithHooks />
|
||||
<ForwardRefWithHooks />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
// Below copied from https://usehooks.com/
|
||||
function useDebounce(value, delay) {
|
||||
// State and setters for debounced value
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
// Show the value in DevTools
|
||||
useDebugValue(debouncedValue);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
// Update debounced value after delay
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
// Cancel the timeout if value changes (also on delay change or unmount)
|
||||
// This is how we prevent debounced value from updating if value is changed ...
|
||||
// .. within the delay period. Timeout gets cleared and restarted.
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
},
|
||||
[value, delay] // Only re-call effect if value or delay changes
|
||||
);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
// Above copied from https://usehooks.com/
|
||||
@@ -1,16 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
type Props = {|
|
||||
initialCount: number,
|
||||
|};
|
||||
|
||||
export default function FunctionWithState({ initialCount }: Props) {
|
||||
const [count, setCount] = useState(initialCount);
|
||||
const handleClick = useCallback(() => {
|
||||
setCount(count => count + 1);
|
||||
});
|
||||
|
||||
return <button onClick={handleClick}>Count {count}</button>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.Header {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
@@ -2,15 +2,19 @@
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import Contexts from './Contexts';
|
||||
import FunctionWithState from './FunctionWithState';
|
||||
import CustomHooks from './CustomHooks';
|
||||
import NestedProps from './NestedProps';
|
||||
import styles from './InspectableElements.css';
|
||||
|
||||
// TODO Add Immutable JS example
|
||||
|
||||
export default function InspectableElements() {
|
||||
return (
|
||||
<Fragment>
|
||||
<FunctionWithState initialCount={1} />
|
||||
<div className={styles.Header}>Inspectable elements</div>
|
||||
<NestedProps />
|
||||
<Contexts />
|
||||
<CustomHooks />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1103,7 +1103,7 @@ export function attach(
|
||||
canEditValues: false, // TODO
|
||||
|
||||
// Inspectable properties.
|
||||
// TODO Sanitize props, state, and context
|
||||
// TODO Review sanitization approach for the below inspectable values.
|
||||
context,
|
||||
hooks: usesHooks
|
||||
? cleanForBridge(
|
||||
|
||||
@@ -80,9 +80,3 @@ export type HooksNode = {
|
||||
subHooks: Array<HooksNode>,
|
||||
};
|
||||
export type HooksTree = Array<HooksNode>;
|
||||
|
||||
export type InspectedHooks = {|
|
||||
elementID: string,
|
||||
id: string,
|
||||
hooksTree: HooksTree,
|
||||
|};
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
.HooksTree {
|
||||
.HooksTreeView {
|
||||
padding: 0.25rem;
|
||||
border-bottom: 1px solid var(--color-base02);
|
||||
}
|
||||
|
||||
.ComingSoon {
|
||||
.HooksNode {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.NameValueRow {
|
||||
}
|
||||
|
||||
.Name {
|
||||
color: var(--color-tree-attr-name);
|
||||
}
|
||||
|
||||
.Value {
|
||||
color: var(--color-tree-attr-value);
|
||||
}
|
||||
|
||||
.None {
|
||||
color: var(--color-base03);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,65 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { getMetaValueLabel } from './utils';
|
||||
import styles from './HooksTree.css';
|
||||
|
||||
import type { InspectedHooks } from 'src/backend/types';
|
||||
import type { HooksNode, HooksTree } from 'src/backend/types';
|
||||
|
||||
type Props = {|
|
||||
inspectedHooks: InspectedHooks | null,
|
||||
|};
|
||||
|
||||
export default function HooksTree({ inspectedHooks }: Props) {
|
||||
if (inspectedHooks === null) {
|
||||
export function HooksTreeView({ hooksTree }: { hooksTree: HooksTree | null }) {
|
||||
if (hooksTree === null) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<div className={styles.HooksTreeView}>
|
||||
<div className={styles.Item}>hooks</div>
|
||||
<InnerHooksTreeView hooksTree={hooksTree} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function InnerHooksTreeView({ hooksTree }: { hooksTree: HooksTree }) {
|
||||
// $FlowFixMe "Missing type annotation for U" whatever that means
|
||||
return hooksTree.map((hooksNode, index) => (
|
||||
<HooksNodeView key={index} hooksNode={hooksTree[index]} />
|
||||
));
|
||||
}
|
||||
|
||||
function HooksNodeView({ hooksNode }: { hooksNode: HooksNode }) {
|
||||
const { name, subHooks, value } = hooksNode;
|
||||
|
||||
// TODO Add click and key handlers for toggling element open/close state.
|
||||
// TODO Support editable props
|
||||
|
||||
const isCustomHook = subHooks.length > 0;
|
||||
|
||||
// Format data for display to mimic the props/state/context for now.
|
||||
const type = typeof value;
|
||||
let displayValue;
|
||||
if (isCustomHook && value === undefined) {
|
||||
displayValue = null;
|
||||
} else if (
|
||||
type === 'number' ||
|
||||
type === 'string' ||
|
||||
type === 'boolean' ||
|
||||
value == null
|
||||
) {
|
||||
displayValue = value;
|
||||
} else {
|
||||
displayValue = getMetaValueLabel(value);
|
||||
}
|
||||
|
||||
// TODO
|
||||
return (
|
||||
<div className={styles.HooksTree}>
|
||||
hooks
|
||||
<div className={styles.ComingSoon}>Coming soon...</div>
|
||||
<div className={styles.HooksNode}>
|
||||
<div className={styles.NameValueRow}>
|
||||
<span className={styles.Name}>{name}: </span> {/* $FlowFixMe */}
|
||||
<span className={styles.Value}>{displayValue}</span>
|
||||
</div>
|
||||
<InnerHooksTreeView hooksTree={subHooks} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
export default React.memo(HooksTreeView);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { getMetaValueLabel } from './utils';
|
||||
import { meta } from '../../hydration';
|
||||
import styles from './InspectedElementTree.css';
|
||||
|
||||
@@ -15,7 +16,6 @@ export default function InspectedElementTree({ data, label }: Props) {
|
||||
} else {
|
||||
// TODO Add click and key handlers for toggling element open/close state.
|
||||
// TODO Support editable props
|
||||
|
||||
return (
|
||||
<div className={styles.InspectedElementTree}>
|
||||
<div className={styles.Item}>{label}</div>
|
||||
@@ -108,24 +108,3 @@ function KeyValue({ depth, name, value }: KeyValueProps) {
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
function getMetaValueLabel(data: Object): string | null {
|
||||
switch (data[meta.type]) {
|
||||
case 'function':
|
||||
return `${data[meta.name] || 'fn'}()`;
|
||||
case 'object':
|
||||
return 'Object';
|
||||
case 'date':
|
||||
case 'symbol':
|
||||
return data[meta.name];
|
||||
case 'iterator':
|
||||
return `${data[meta.name]}(…)`;
|
||||
case 'array_buffer':
|
||||
case 'data_view':
|
||||
case 'array':
|
||||
case 'typed_array':
|
||||
return `${data[meta.name]}[${data[meta.meta].length}]`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ function InspectedElementView({
|
||||
<div className={styles.InspectedElement}>
|
||||
<InspectedElementTree label="props" data={props} />
|
||||
<InspectedElementTree label="state" data={state} />
|
||||
<HooksTree inspectedHooks={hooks} />
|
||||
<HooksTree hooksTree={hooks} />
|
||||
<InspectedElementTree label="context" data={context} />
|
||||
|
||||
{owners !== null && owners.length > 0 && (
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// @flow
|
||||
|
||||
import { meta } from '../../hydration';
|
||||
|
||||
export function getMetaValueLabel(data: Object): string | null {
|
||||
switch (data[meta.type]) {
|
||||
case 'function':
|
||||
return `${data[meta.name] || 'fn'}()`;
|
||||
case 'object':
|
||||
return 'Object';
|
||||
case 'date':
|
||||
case 'symbol':
|
||||
return data[meta.name];
|
||||
case 'iterator':
|
||||
return `${data[meta.name]}(…)`;
|
||||
case 'array_buffer':
|
||||
case 'data_view':
|
||||
case 'array':
|
||||
case 'typed_array':
|
||||
return `${data[meta.name]}[${data[meta.meta].length}]`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user