DevTools: Show hook names based on variable usage (#21641)

Co-authored-by: Brian Vaughn <brian.david.vaughn@gmail.com>
Co-authored-by: Saphal Patro <saphal1998@gmail.com>
Co-authored-by: VibhorCodecianGupta <vibhordelgupta@gmail.com>
This commit is contained in:
Brian Vaughn
2021-07-01 14:39:18 -04:00
committed by GitHub
co-authored by Brian Vaughn Saphal Patro VibhorCodecianGupta
parent ab390c65ee
commit c5cfa71948
84 changed files with 2966 additions and 74 deletions
@@ -26,6 +26,7 @@ export type IconType =
| 'log-data'
| 'more'
| 'next'
| 'parse-hook-names'
| 'previous'
| 'record'
| 'reload'
@@ -92,6 +93,9 @@ export default function ButtonIcon({className = '', type}: Props) {
case 'next':
pathData = PATH_NEXT;
break;
case 'parse-hook-names':
pathData = PATH_PARSE_HOOK_NAMES;
break;
case 'previous':
pathData = PATH_PREVIOUS;
break;
@@ -141,7 +145,11 @@ export default function ButtonIcon({className = '', type}: Props) {
height="24"
viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none" />
<path fill="currentColor" d={pathData} />
{typeof pathData === 'string' ? (
<path fill="currentColor" d={pathData} />
) : (
pathData
)}
</svg>
);
}
@@ -197,6 +205,15 @@ const PATH_MORE = `
const PATH_NEXT = 'M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z';
const PATH_PARSE_HOOK_NAMES = (
<g>
<polygon points="20,7 20.94,4.94 23,4 20.94,3.06 20,1 19.06,3.06 17,4 19.06,4.94" />
<polygon points="8.5,7 9.44,4.94 11.5,4 9.44,3.06 8.5,1 7.56,3.06 5.5,4 7.56,4.94" />
<polygon points="20,12.5 19.06,14.56 17,15.5 19.06,16.44 20,18.5 20.94,16.44 23,15.5 20.94,14.56" />
<path d="M17.71,9.12l-2.83-2.83C14.68,6.1,14.43,6,14.17,6c-0.26,0-0.51,0.1-0.71,0.29L2.29,17.46c-0.39,0.39-0.39,1.02,0,1.41 l2.83,2.83C5.32,21.9,5.57,22,5.83,22s0.51-0.1,0.71-0.29l11.17-11.17C18.1,10.15,18.1,9.51,17.71,9.12z M14.17,8.42l1.41,1.41 L14.41,11L13,9.59L14.17,8.42z M5.83,19.59l-1.41-1.41L11.59,11L13,12.41L5.83,19.59z" />
</g>
);
const PATH_PREVIOUS =
'M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z';
@@ -26,7 +26,7 @@ export default function Badge({
type,
children,
}: Props) {
if (hocDisplayNames === null) {
if (hocDisplayNames === null || hocDisplayNames.length === 0) {
return null;
}
@@ -68,4 +68,5 @@
justify-content: center;
font-size: var(--font-size-sans-large);
color: var(--color-dim);
border-left: 1px solid var(--color-border);
}
@@ -64,5 +64,5 @@
padding: 0.25rem;
color: var(--color-dimmer);
font-style: italic;
border-left: 1px solid var(--color-border);
}
@@ -39,7 +39,12 @@ export default function InspectedElementWrapper(_: Props) {
const store = useContext(StoreContext);
const {dispatch: modalDialogDispatch} = useContext(ModalDialogContext);
const {inspectedElement} = useContext(InspectedElementContext);
const {
hookNames,
inspectedElement,
parseHookNames,
toggleParseHookNames,
} = useContext(InspectedElementContext);
const element =
inspectedElementID !== null
@@ -268,7 +273,10 @@ export default function InspectedElementWrapper(_: Props) {
inspectedElementID /* Force reset when selected Element changes */
}
element={element}
hookNames={hookNames}
inspectedElement={inspectedElement}
parseHookNames={parseHookNames}
toggleParseHookNames={toggleParseHookNames}
/>
)}
</div>
@@ -18,13 +18,19 @@ import {
useMemo,
useState,
} from 'react';
import {enableHookNameParsing} from 'react-devtools-feature-flags';
import {TreeStateContext} from './TreeContext';
import {BridgeContext, StoreContext} from '../context';
import {
checkForUpdate,
inspectElement,
} from 'react-devtools-shared/src/inspectedElementCache';
import {loadHookNames} from 'react-devtools-shared/src/hookNamesCache';
import {ElementTypeFunction} from 'react-devtools-shared/src/types';
import LoadHookNamesFunctionContext from 'react-devtools-shared/src/devtools/views/Components/LoadHookNamesFunctionContext';
import {SettingsContext} from '../Settings/SettingsContext';
import type {HookNames} from 'react-devtools-shared/src/types';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {
Element,
@@ -33,10 +39,14 @@ import type {
type Path = Array<string | number>;
type InspectPathFunction = (path: Path) => void;
export type ToggleParseHookNames = () => void;
type Context = {|
hookNames: HookNames | null,
inspectedElement: InspectedElement | null,
inspectPaths: InspectPathFunction,
parseHookNames: boolean,
toggleParseHookNames: ToggleParseHookNames,
|};
export const InspectedElementContext = createContext<Context>(
@@ -51,8 +61,10 @@ export type Props = {|
export function InspectedElementContextController({children}: Props) {
const {selectedElementID} = useContext(TreeStateContext);
const loadHookNamesFunction = useContext(LoadHookNamesFunctionContext);
const bridge = useContext(BridgeContext);
const store = useContext(StoreContext);
const {parseHookNames: parseHookNamesByDefault} = useContext(SettingsContext);
const refresh = useCacheRefresh();
@@ -68,6 +80,13 @@ export function InspectedElementContextController({children}: Props) {
path: null,
});
// Parse the currently inspected element's hook names.
// This may be enabled by default (for all elements)
// or it may be opted into on a per-element basis (if it's too slow to be on by default).
const [parseHookNames, setParseHookNames] = useState<boolean>(
parseHookNamesByDefault,
);
const element =
selectedElementID !== null ? store.getElementByID(selectedElementID) : null;
@@ -79,14 +98,41 @@ export function InspectedElementContextController({children}: Props) {
element,
path: null,
});
setParseHookNames(parseHookNamesByDefault);
}
// Don't load a stale element from the backend; it wastes bridge bandwidth.
let inspectedElement = null;
let hookNames: HookNames | null = null;
if (!elementHasChanged && element !== null) {
inspectedElement = inspectElement(element, state.path, store, bridge);
if (enableHookNameParsing) {
if (parseHookNames) {
if (
inspectedElement !== null &&
inspectedElement.type === ElementTypeFunction &&
inspectedElement.hooks !== null &&
loadHookNamesFunction !== null
) {
hookNames = loadHookNames(
element,
inspectedElement.hooks,
loadHookNamesFunction,
);
}
}
}
}
const toggleParseHookNames: ToggleParseHookNames = useCallback<ToggleParseHookNames>(() => {
startTransition(() => {
setParseHookNames(value => !value);
refresh();
});
}, [setParseHookNames]);
const inspectPaths: InspectPathFunction = useCallback<InspectPathFunction>(
(path: Path) => {
startTransition(() => {
@@ -125,6 +171,7 @@ export function InspectedElementContextController({children}: Props) {
}
}, [
element,
hookNames,
// Reset this timer any time the element we're inspecting gets a new response.
// No sense to ping right away after e.g. inspecting/hydrating a path.
inspectedElement,
@@ -133,10 +180,19 @@ export function InspectedElementContextController({children}: Props) {
const value = useMemo<Context>(
() => ({
hookNames,
inspectedElement,
inspectPaths,
parseHookNames,
toggleParseHookNames,
}),
[inspectedElement, inspectPaths],
[
hookNames,
inspectedElement,
inspectPaths,
parseHookNames,
toggleParseHookNames,
],
);
return (
@@ -1,3 +1,3 @@
.Wrapper {
border-left: 1px solid var(--color-border);
height: 100%;
}
@@ -77,4 +77,8 @@
margin-right: 0.25rem;
border-radius: 0.125rem;
padding: 0.125rem 0.25rem;
}
.HookName {
color: var(--color-component-name);
}
@@ -13,6 +13,7 @@ import {useCallback, useContext, useRef, useState} from 'react';
import {BridgeContext, StoreContext} from '../context';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';
import Toggle from '../Toggle';
import ExpandCollapseToggle from './ExpandCollapseToggle';
import KeyValue from './KeyValue';
import {getMetaValueLabel, serializeHooksForCopy} from '../utils';
@@ -20,28 +21,49 @@ import Store from '../../store';
import styles from './InspectedElementHooksTree.css';
import useContextMenu from '../../ContextMenu/useContextMenu';
import {meta} from '../../../hydration';
import {enableProfilerChangedHookIndices} from 'react-devtools-feature-flags';
import {
enableHookNameParsing,
enableProfilerChangedHookIndices,
} from 'react-devtools-feature-flags';
import type {InspectedElement} from './types';
import type {HooksNode, HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {HookNames} from 'react-devtools-shared/src/types';
import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';
import type {ToggleParseHookNames} from './InspectedElementContext';
type HooksTreeViewProps = {|
bridge: FrontendBridge,
element: Element,
hookNames: HookNames | null,
inspectedElement: InspectedElement,
parseHookNames: boolean,
store: Store,
toggleParseHookNames: ToggleParseHookNames,
|};
export function InspectedElementHooksTree({
bridge,
element,
hookNames,
inspectedElement,
parseHookNames,
store,
toggleParseHookNames,
}: HooksTreeViewProps) {
const {hooks, id} = inspectedElement;
// Changing parseHookNames is done in a transition, because it suspends.
// This value is done outside of the transition, so the UI toggle feels responsive.
const [parseHookNamesOptimistic, setParseHookNamesOptimistic] = useState(
parseHookNames,
);
const handleChange = () => {
setParseHookNamesOptimistic(!parseHookNames);
toggleParseHookNames();
};
const handleCopy = () => copy(serializeHooksForCopy(hooks));
if (hooks === null) {
@@ -51,11 +73,25 @@ export function InspectedElementHooksTree({
<div className={styles.HooksTreeView}>
<div className={styles.HeaderRow}>
<div className={styles.Header}>hooks</div>
{enableHookNameParsing && !parseHookNames && (
<Toggle
isChecked={parseHookNamesOptimistic}
isDisabled={parseHookNamesOptimistic}
onChange={handleChange}
title={
parseHookNames
? 'Parse hook names'
: 'Parse hook names (may be slow)'
}>
<ButtonIcon type="parse-hook-names" />
</Toggle>
)}
<Button onClick={handleCopy} title="Copy to clipboard">
<ButtonIcon type="copy" />
</Button>
</div>
<InnerHooksTreeView
hookNames={hookNames}
hooks={hooks}
id={id}
element={element}
@@ -69,6 +105,7 @@ export function InspectedElementHooksTree({
type InnerHooksTreeViewProps = {|
element: Element,
hookNames: HookNames | null,
hooks: HooksTree,
id: number,
inspectedElement: InspectedElement,
@@ -77,6 +114,7 @@ type InnerHooksTreeViewProps = {|
export function InnerHooksTreeView({
element,
hookNames,
hooks,
id,
inspectedElement,
@@ -88,6 +126,7 @@ export function InnerHooksTreeView({
key={index}
element={element}
hook={hooks[index]}
hookNames={hookNames}
id={id}
inspectedElement={inspectedElement}
path={path.concat([index])}
@@ -98,12 +137,20 @@ export function InnerHooksTreeView({
type HookViewProps = {|
element: Element,
hook: HooksNode,
hookNames: HookNames | null,
id: number,
inspectedElement: InspectedElement,
path: Array<string | number>,
|};
function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
function HookView({
element,
hook,
hookNames,
id,
inspectedElement,
path,
}: HookViewProps) {
const {
canEditHooks,
canEditHooksAndDeletePaths,
@@ -180,6 +227,16 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
let displayValue;
let isComplexDisplayValue = false;
const hookName = hookNames != null ? hookNames.get(hook) : null;
const hookDisplayName = hookName ? (
<>
{name}
{!!hookName && <span className={styles.HookName}>({hookName})</span>}
</>
) : (
name
);
// Format data for display to mimic the props/state/context for now.
if (type === 'string') {
displayValue = `"${((value: any): string)}"`;
@@ -204,6 +261,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
<InnerHooksTreeView
element={element}
hooks={subHooks}
hookNames={hookNames}
id={id}
inspectedElement={inspectedElement}
path={path.concat(['subHooks'])}
@@ -219,6 +277,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
depth={1}
element={element}
hookID={hookID}
hookName={hookName}
inspectedElement={inspectedElement}
name="subHooks"
path={path.concat(['subHooks'])}
@@ -236,7 +295,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
<span
onClick={toggleIsOpen}
className={name !== '' ? styles.Name : styles.NameAnonymous}>
{name || 'Anonymous'}
{hookDisplayName || 'Anonymous'}
</span>
<span className={styles.Value} onClick={toggleIsOpen}>
{isOpen || getMetaValueLabel(value)}
@@ -253,6 +312,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
depth={1}
element={element}
hookID={hookID}
hookName={hookName}
inspectedElement={inspectedElement}
name="DebugValue"
path={path.concat(['value'])}
@@ -272,7 +332,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
<span
onClick={toggleIsOpen}
className={name !== '' ? styles.Name : styles.NameAnonymous}>
{name || 'Anonymous'}
{hookDisplayName || 'Anonymous'}
</span>{' '}
{/* $FlowFixMe */}
<span className={styles.Value} onClick={toggleIsOpen}>
@@ -299,6 +359,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
depth={1}
element={element}
hookID={hookID}
hookName={hookName}
inspectedElement={inspectedElement}
name={name}
path={path.concat(['value'])}
@@ -320,6 +381,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
depth={1}
element={element}
hookID={hookID}
hookName={hookName}
inspectedElement={inspectedElement}
name={name}
path={[]}
@@ -36,19 +36,26 @@ import styles from './InspectedElementView.css';
import type {ContextMenuContextType} from '../context';
import type {Element, InspectedElement, SerializedElement} from './types';
import type {ElementType} from 'react-devtools-shared/src/types';
import type {ElementType, HookNames} from 'react-devtools-shared/src/types';
import type {ToggleParseHookNames} from './InspectedElementContext';
export type CopyPath = (path: Array<string | number>) => void;
export type InspectPath = (path: Array<string | number>) => void;
type Props = {|
element: Element,
hookNames: HookNames | null,
inspectedElement: InspectedElement,
parseHookNames: boolean,
toggleParseHookNames: ToggleParseHookNames,
|};
export default function InspectedElementView({
element,
hookNames,
inspectedElement,
parseHookNames,
toggleParseHookNames,
}: Props) {
const {id} = element;
const {
@@ -103,8 +110,11 @@ export default function InspectedElementView({
<InspectedElementHooksTree
bridge={bridge}
element={element}
hookNames={hookNames}
inspectedElement={inspectedElement}
parseHookNames={parseHookNames}
store={store}
toggleParseHookNames={toggleParseHookNames}
/>
<InspectedElementContextTree
@@ -11,6 +11,7 @@
flex: 0 0 auto;
cursor: default;
}
.EditableName {
color: var(--color-attribute-name);
flex: 0 0 auto;
@@ -48,4 +49,8 @@
.DeleteArrayItemButton {
padding: 0;
margin-right: 0.125rem;
}
.HookName {
color: var(--color-component-name);
}
@@ -43,6 +43,7 @@ type KeyValueProps = {|
element: Element,
hidden: boolean,
hookID?: ?number,
hookName?: ?string,
inspectedElement: InspectedElement,
isDirectChildOfAnArray?: boolean,
name: string,
@@ -65,6 +66,7 @@ export default function KeyValue({
isDirectChildOfAnArray,
hidden,
hookID,
hookName,
name,
path,
pathRoot,
@@ -202,7 +204,12 @@ export default function KeyValue({
<DeleteToggle name={name} deletePath={deletePath} path={path} />
);
} else {
renderedName = <span className={styles.Name}>{name}</span>;
renderedName = (
<span className={styles.Name}>
{name}
{!!hookName && <span className={styles.HookName}>({hookName})</span>}
</span>
);
}
} else if (canRenameTheCurrentPath) {
renderedName = (
@@ -215,7 +222,12 @@ export default function KeyValue({
/>
);
} else {
renderedName = <span className={styles.Name}>{name}</span>;
renderedName = (
<span className={styles.Name}>
{name}
{!!hookName && <span className={styles.HookName}>({hookName})</span>}
</span>
);
}
let children = null;
@@ -0,0 +1,11 @@
// @flow
import {createContext} from 'react';
import type {LoadHookNamesFunction} from '../DevTools';
export type Context = LoadHookNamesFunction | null;
const LoadHookNamesFunctionContext = createContext<Context>(null);
LoadHookNamesFunctionContext.displayName = 'LoadHookNamesFunctionContext';
export default LoadHookNamesFunctionContext;
+48 -32
View File
@@ -22,6 +22,7 @@ import TabBar from './TabBar';
import {SettingsContextController} from './Settings/SettingsContext';
import {TreeContextController} from './Components/TreeContext';
import ViewElementSourceContext from './Components/ViewElementSourceContext';
import LoadHookNamesFunctionContext from './Components/LoadHookNamesFunctionContext';
import {ProfilerContextController} from './Profiler/ProfilerContext';
import {ModalDialogContextController} from './ModalDialog';
import ReactLogo from './ReactLogo';
@@ -34,15 +35,22 @@ import styles from './DevTools.css';
import './root.css';
import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {HookNames} from 'react-devtools-shared/src/types';
import type {Thenable} from '../cache';
export type BrowserTheme = 'dark' | 'light';
export type TabID = 'components' | 'profiler';
export type ViewElementSource = (
id: number,
inspectedElement: InspectedElement,
) => void;
export type LoadHookNamesFunction = (
hooksTree: HooksTree,
) => Thenable<HookNames>;
export type ViewAttributeSource = (
id: number,
path: Array<string | number>,
@@ -75,6 +83,11 @@ export type Props = {|
// but individual tabs (e.g. Components, Profiling) can be rendered into portals within their browser panels.
componentsPortalContainer?: Element,
profilerPortalContainer?: Element,
// Loads and parses source maps for function components
// and extracts hook "names" based on the variables the hook return values get assigned to.
// Not every DevTools build can load source maps, so this property is optional.
loadHookNamesFunction?: ?LoadHookNamesFunction,
|};
const componentsTab = {
@@ -99,6 +112,7 @@ export default function DevTools({
componentsPortalContainer,
defaultTab = 'components',
enabledInspectedElementContextMenu = false,
loadHookNamesFunction,
overrideTab,
profilerPortalContainer,
showTabBar = false,
@@ -180,7 +194,6 @@ export default function DevTools({
}
};
}, [bridge]);
return (
<BridgeContext.Provider value={bridge}>
<StoreContext.Provider value={store}>
@@ -191,40 +204,43 @@ export default function DevTools({
componentsPortalContainer={componentsPortalContainer}
profilerPortalContainer={profilerPortalContainer}>
<ViewElementSourceContext.Provider value={viewElementSource}>
<TreeContextController>
<ProfilerContextController>
<div className={styles.DevTools} ref={devToolsRef}>
{showTabBar && (
<div className={styles.TabBar}>
<ReactLogo />
<span className={styles.DevToolsVersion}>
{process.env.DEVTOOLS_VERSION}
</span>
<div className={styles.Spacer} />
<TabBar
currentTab={tab}
id="DevTools"
selectTab={setTab}
tabs={tabs}
type="navigation"
<LoadHookNamesFunctionContext.Provider
value={loadHookNamesFunction || null}>
<TreeContextController>
<ProfilerContextController>
<div className={styles.DevTools} ref={devToolsRef}>
{showTabBar && (
<div className={styles.TabBar}>
<ReactLogo />
<span className={styles.DevToolsVersion}>
{process.env.DEVTOOLS_VERSION}
</span>
<div className={styles.Spacer} />
<TabBar
currentTab={tab}
id="DevTools"
selectTab={setTab}
tabs={tabs}
type="navigation"
/>
</div>
)}
<div
className={styles.TabContent}
hidden={tab !== 'components'}>
<Components
portalContainer={componentsPortalContainer}
/>
</div>
)}
<div
className={styles.TabContent}
hidden={tab !== 'components'}>
<Components
portalContainer={componentsPortalContainer}
/>
<div
className={styles.TabContent}
hidden={tab !== 'profiler'}>
<Profiler portalContainer={profilerPortalContainer} />
</div>
</div>
<div
className={styles.TabContent}
hidden={tab !== 'profiler'}>
<Profiler portalContainer={profilerPortalContainer} />
</div>
</div>
</ProfilerContextController>
</TreeContextController>
</ProfilerContextController>
</TreeContextController>
</LoadHookNamesFunctionContext.Provider>
</ViewElementSourceContext.Provider>
</SettingsContextController>
<UnsupportedBridgeProtocolDialog />
@@ -16,11 +16,13 @@ import {
useRef,
useState,
} from 'react';
import {enableHookNameParsing} from 'react-devtools-feature-flags';
import {useSubscription} from '../hooks';
import {StoreContext} from '../context';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';
import Toggle from '../Toggle';
import {SettingsContext} from '../Settings/SettingsContext';
import {
ComponentFilterDisplayName,
ComponentFilterElementType,
@@ -50,6 +52,7 @@ import type {
export default function ComponentsSettings(_: {||}) {
const store = useContext(StoreContext);
const {parseHookNames, setParseHookNames} = useContext(SettingsContext);
const collapseNodesByDefaultSubscription = useMemo(
() => ({
@@ -72,6 +75,13 @@ export default function ComponentsSettings(_: {||}) {
[store],
);
const updateParseHookNames = useCallback(
({currentTarget}) => {
setParseHookNames(currentTarget.checked);
},
[setParseHookNames],
);
const [componentFilters, setComponentFilters] = useState<
Array<ComponentFilter>,
>(() => [...store.componentFilters]);
@@ -252,6 +262,18 @@ export default function ComponentsSettings(_: {||}) {
Expand component tree by default
</label>
{enableHookNameParsing && (
<label className={styles.Setting}>
<input
type="checkbox"
checked={parseHookNames}
onChange={updateParseHookNames}
/>{' '}
Always parse hook names from source{' '}
<span className={styles.Warning}>(may be slow)</span>
</label>
)}
<div className={styles.Header}>Hide components where...</div>
<table className={styles.Table}>
@@ -18,6 +18,7 @@ import {
import {
COMFORTABLE_LINE_HEIGHT,
COMPACT_LINE_HEIGHT,
LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY,
LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY,
@@ -45,6 +46,9 @@ type Context = {|
breakOnConsoleErrors: boolean,
setBreakOnConsoleErrors: (value: boolean) => void,
parseHookNames: boolean,
setParseHookNames: (value: boolean) => void,
showInlineWarningsAndErrors: boolean,
setShowInlineWarningsAndErrors: (value: boolean) => void,
@@ -94,6 +98,10 @@ function SettingsContextController({
LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
false,
);
const [parseHookNames, setParseHookNames] = useLocalStorage<boolean>(
LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY,
false,
);
const [
showInlineWarningsAndErrors,
setShowInlineWarningsAndErrors,
@@ -180,9 +188,11 @@ function SettingsContextController({
displayDensity === 'compact'
? COMPACT_LINE_HEIGHT
: COMFORTABLE_LINE_HEIGHT,
parseHookNames,
setAppendComponentStack,
setBreakOnConsoleErrors,
setDisplayDensity,
setParseHookNames,
setTheme,
setTraceUpdatesEnabled,
setShowInlineWarningsAndErrors,
@@ -194,9 +204,11 @@ function SettingsContextController({
appendComponentStack,
breakOnConsoleErrors,
displayDensity,
parseHookNames,
setAppendComponentStack,
setBreakOnConsoleErrors,
setDisplayDensity,
setParseHookNames,
setTheme,
setTraceUpdatesEnabled,
setShowInlineWarningsAndErrors,
@@ -139,3 +139,7 @@
.ReleaseNotesLink {
color: var(--color-button-active);
}
.Warning {
color: var(--color-error-text);
}