Remove UNSAFE methods from react-json-tree (#1288)

* Remove UNSAFE method from JSONTree

* Bump peer dep

* Fix types

* Remove proptypes

* Remove unused

* shouldExpandNode => shouldExpandNodeInitially

* Cleanup

* Update usages

* Tighten types

* Create four-parrots-poke.md

* Format

* Fix inspector-monitor types

* Fix log-monitor types

* Fix rtk-query-monitor types

* Fix type
This commit is contained in:
Nathan Bierema 2023-01-04 23:17:44 -05:00 committed by GitHub
parent 5f33eeb8bf
commit 81926f3212
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 413 additions and 645 deletions

View File

@ -0,0 +1,9 @@
---
'react-json-tree': major
---
Remove UNSAFE method from react-json-tree
- Replace `shouldExpandNode` with `shouldExpandNodeInitially`. This function is now only called when a node in the tree is first rendered, when before it would update the expanded state of the node if the results of calling `shouldExpandNode` changed between renders. There is no way to replicate the old behavior exactly, but the new behavior is the intended behavior for the use cases within Redux DevTools. Please open an issue if you need a way to programatically control the expanded state of nodes.
- Bump the minimum React version from `16.3.0` to `16.8.0` so that `react-json-tree` can use hooks.
- Tightened TypeScript prop types to use `unknown` instead of `any` where possible and make the key path array `readonly`.

View File

@ -139,7 +139,7 @@ Their full signatures are:
#### More Options #### More Options
- `shouldExpandNode: function(keyPath, data, level)` - determines if node should be expanded (root is expanded by default) - `shouldExpandNodeInitially: function(keyPath, data, level)` - determines if node should be expanded when it first renders (root is expanded by default)
- `hideRoot: boolean` - if `true`, the root node is hidden. - `hideRoot: boolean` - if `true`, the root node is hidden.
- `sortObjectKeys: boolean | function(a, b)` - sorts object keys with compare function (optional). Isn't applied to iterable maps like `Immutable.Map`. - `sortObjectKeys: boolean | function(a, b)` - sorts object keys with compare function (optional). Isn't applied to iterable maps like `Immutable.Map`.
- `postprocessValue: function(value)` - maps `value` to a new `value` - `postprocessValue: function(value)` - maps `value` to a new `value`

View File

@ -178,7 +178,7 @@ const App = () => (
<span role="img" aria-label="mellow"> <span role="img" aria-label="mellow">
😐 😐
</span>{' '} </span>{' '}
{raw}{' '} {raw as string}{' '}
<span role="img" aria-label="mellow"> <span role="img" aria-label="mellow">
😐 😐
</span> </span>
@ -194,7 +194,11 @@ const App = () => (
</div> </div>
<p>Collapsed root node</p> <p>Collapsed root node</p>
<div> <div>
<JSONTree data={data} theme={theme} shouldExpandNode={() => false} /> <JSONTree
data={data}
theme={theme}
shouldExpandNodeInitially={() => false}
/>
</div> </div>
</div> </div>
); );

View File

@ -47,8 +47,6 @@
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.6", "@babel/runtime": "^7.20.6",
"@types/lodash": "^4.14.191", "@types/lodash": "^4.14.191",
"@types/prop-types": "^15.7.5",
"prop-types": "^15.8.1",
"react-base16-styling": "^0.9.1" "react-base16-styling": "^0.9.1"
}, },
"devDependencies": { "devDependencies": {
@ -85,7 +83,7 @@
"typescript": "~4.9.4" "typescript": "~4.9.4"
}, },
"peerDependencies": { "peerDependencies": {
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0", "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.3.0 || ^17.0.0 || ^18.0.0" "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
} }
} }

View File

@ -1,59 +1,39 @@
import React from 'react'; import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import JSONArrow from './JSONArrow'; import JSONArrow from './JSONArrow';
import { CircularPropsPassedThroughItemRange } from './types'; import type { CircularCache, CommonInternalProps } from './types';
interface Props extends CircularPropsPassedThroughItemRange { interface Props extends CommonInternalProps {
data: any; data: unknown;
nodeType: string; nodeType: string;
from: number; from: number;
to: number; to: number;
renderChildNodes: (props: Props, from: number, to: number) => React.ReactNode; renderChildNodes: (props: Props, from: number, to: number) => React.ReactNode;
circularCache: CircularCache;
level: number;
} }
interface State { export default function ItemRange(props: Props) {
expanded: boolean; const { styling, from, to, renderChildNodes, nodeType } = props;
}
const [expanded, setExpanded] = useState<boolean>(false);
export default class ItemRange extends React.Component<Props, State> { const handleClick = useCallback(() => {
static propTypes = { setExpanded(!expanded);
styling: PropTypes.func.isRequired, }, [expanded]);
from: PropTypes.number.isRequired,
to: PropTypes.number.isRequired, return expanded ? (
renderChildNodes: PropTypes.func.isRequired, <div {...styling('itemRange', expanded)}>
nodeType: PropTypes.string.isRequired, {renderChildNodes(props, from, to)}
}; </div>
) : (
constructor(props: Props) { <div {...styling('itemRange', expanded)} onClick={handleClick}>
super(props); <JSONArrow
this.state = { expanded: false }; nodeType={nodeType}
} styling={styling}
expanded={false}
render() { onClick={handleClick}
const { styling, from, to, renderChildNodes, nodeType } = this.props; arrowStyle="double"
/>
return this.state.expanded ? ( {`${from} ... ${to}`}
<div {...styling('itemRange', this.state.expanded)}> </div>
{renderChildNodes(this.props, from, to)} );
</div>
) : (
<div
{...styling('itemRange', this.state.expanded)}
onClick={this.handleClick}
>
<JSONArrow
nodeType={nodeType}
styling={styling}
expanded={false}
onClick={this.handleClick}
arrowStyle="double"
/>
{`${from} ... ${to}`}
</div>
);
}
handleClick = () => {
this.setState({ expanded: !this.state.expanded });
};
} }

View File

@ -1,35 +1,30 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import JSONNestedNode from './JSONNestedNode'; import JSONNestedNode from './JSONNestedNode';
import { CircularPropsPassedThroughJSONNode } from './types'; import type { CommonInternalProps } from './types';
// Returns the "n Items" string for this node, // Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet. // generating and caching it if it hasn't been created yet.
function createItemString(data: any) { function createItemString(data: unknown) {
return `${(data as unknown[]).length} ${ return `${(data as unknown[]).length} ${
(data as unknown[]).length !== 1 ? 'items' : 'item' (data as unknown[]).length !== 1 ? 'items' : 'item'
}`; }`;
} }
interface Props extends CircularPropsPassedThroughJSONNode { interface Props extends CommonInternalProps {
data: any; data: unknown;
nodeType: string; nodeType: string;
} }
// Configures <JSONNestedNode> to render an Array // Configures <JSONNestedNode> to render an Array
const JSONArrayNode: React.FunctionComponent<Props> = ({ data, ...props }) => ( export default function JSONArrayNode({ data, ...props }: Props) {
<JSONNestedNode return (
{...props} <JSONNestedNode
data={data} {...props}
nodeType="Array" data={data}
nodeTypeIndicator="[]" nodeType="Array"
createItemString={createItemString} nodeTypeIndicator="[]"
expandable={data.length > 0} createItemString={createItemString}
/> expandable={(data as unknown[]).length > 0}
); />
);
JSONArrayNode.propTypes = { }
data: PropTypes.array,
};
export default JSONArrayNode;

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import type { StylingFunction } from 'react-base16-styling';
import { StylingFunction } from 'react-base16-styling';
interface Props { interface Props {
styling: StylingFunction; styling: StylingFunction;
@ -10,33 +9,21 @@ interface Props {
onClick: React.MouseEventHandler<HTMLDivElement>; onClick: React.MouseEventHandler<HTMLDivElement>;
} }
const JSONArrow: React.FunctionComponent<Props> = ({ export default function JSONArrow({
styling, styling,
arrowStyle, arrowStyle = 'single',
expanded, expanded,
nodeType, nodeType,
onClick, onClick,
}) => ( }: Props) {
<div {...styling('arrowContainer', arrowStyle)} onClick={onClick}> return (
<div {...styling(['arrow', 'arrowSign'], nodeType, expanded, arrowStyle)}> <div {...styling('arrowContainer', arrowStyle)} onClick={onClick}>
{'\u25B6'} <div {...styling(['arrow', 'arrowSign'], nodeType, expanded, arrowStyle)}>
{arrowStyle === 'double' && ( {'\u25B6'}
<div {...styling(['arrowSign', 'arrowSignInner'])}>{'\u25B6'}</div> {arrowStyle === 'double' && (
)} <div {...styling(['arrowSign', 'arrowSignInner'])}>{'\u25B6'}</div>
)}
</div>
</div> </div>
</div> );
); }
JSONArrow.propTypes = {
styling: PropTypes.func.isRequired,
arrowStyle: PropTypes.oneOf(['single', 'double']),
expanded: PropTypes.bool.isRequired,
nodeType: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
};
JSONArrow.defaultProps = {
arrowStyle: 'single',
};
export default JSONArrow;

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import JSONNestedNode from './JSONNestedNode'; import JSONNestedNode from './JSONNestedNode';
import { CircularPropsPassedThroughJSONNode } from './types'; import type { CommonInternalProps } from './types';
// Returns the "n Items" string for this node, // Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet. // generating and caching it if it hasn't been created yet.
@ -22,21 +22,20 @@ function createItemString(data: any, limit: number) {
return `${hasMore ? '>' : ''}${count} ${count !== 1 ? 'entries' : 'entry'}`; return `${hasMore ? '>' : ''}${count} ${count !== 1 ? 'entries' : 'entry'}`;
} }
interface Props extends CircularPropsPassedThroughJSONNode { interface Props extends CommonInternalProps {
data: any; data: unknown;
nodeType: string; nodeType: string;
} }
// Configures <JSONNestedNode> to render an iterable // Configures <JSONNestedNode> to render an iterable
const JSONIterableNode: React.FunctionComponent<Props> = ({ ...props }) => { export default function JSONIterableNode(props: Props) {
return ( return (
<JSONNestedNode <JSONNestedNode
{...props} {...props}
nodeType="Iterable" nodeType="Iterable"
nodeTypeIndicator="()" nodeTypeIndicator="()"
createItemString={createItemString} createItemString={createItemString}
expandable
/> />
); );
}; }
export default JSONIterableNode;

View File

@ -1,22 +1,19 @@
import React from 'react'; import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import JSONArrow from './JSONArrow'; import JSONArrow from './JSONArrow';
import getCollectionEntries from './getCollectionEntries'; import getCollectionEntries from './getCollectionEntries';
import JSONNode from './JSONNode'; import JSONNode from './JSONNode';
import ItemRange from './ItemRange'; import ItemRange from './ItemRange';
import { import type { CircularCache, CommonInternalProps } from './types';
CircularPropsPassedThroughJSONNestedNode,
CircularPropsPassedThroughRenderChildNodes,
} from './types';
/** /**
* Renders nested values (eg. objects, arrays, lists, etc.) * Renders nested values (eg. objects, arrays, lists, etc.)
*/ */
export interface RenderChildNodesProps export interface RenderChildNodesProps extends CommonInternalProps {
extends CircularPropsPassedThroughRenderChildNodes { data: unknown;
data: any;
nodeType: string; nodeType: string;
circularCache: CircularCache;
level: number;
} }
interface Range { interface Range {
@ -26,7 +23,7 @@ interface Range {
interface Entry { interface Entry {
key: string | number; key: string | number;
value: any; value: unknown;
} }
function isRange(rangeOrEntry: Range | Entry): rangeOrEntry is Range { function isRange(rangeOrEntry: Range | Entry): rangeOrEntry is Range {
@ -89,152 +86,92 @@ function renderChildNodes(
return childNodes; return childNodes;
} }
interface Props extends CircularPropsPassedThroughJSONNestedNode { interface Props extends CommonInternalProps {
data: any; data: unknown;
nodeType: string; nodeType: string;
nodeTypeIndicator: string; nodeTypeIndicator: string;
createItemString: (data: any, collectionLimit: number) => string; createItemString: (data: unknown, collectionLimit: number) => string;
expandable: boolean; expandable: boolean;
} }
interface State { export default function JSONNestedNode(props: Props) {
expanded: boolean; const {
} circularCache = [],
collectionLimit,
createItemString,
data,
expandable,
getItemString,
hideRoot,
isCircular,
keyPath,
labelRenderer,
level = 0,
nodeType,
nodeTypeIndicator,
shouldExpandNodeInitially,
styling,
} = props;
function getStateFromProps(props: Props) { const [expanded, setExpanded] = useState<boolean>(
// calculate individual node expansion if necessary // calculate individual node expansion if necessary
const expanded = !props.isCircular isCircular ? false : shouldExpandNodeInitially(keyPath, data, level)
? props.shouldExpandNode(props.keyPath, props.data, props.level) );
: false;
return {
expanded,
};
}
export default class JSONNestedNode extends React.Component<Props, State> { const handleClick = useCallback(() => {
static propTypes = { if (expandable) setExpanded(!expanded);
getItemString: PropTypes.func.isRequired, }, [expandable, expanded]);
nodeTypeIndicator: PropTypes.any,
nodeType: PropTypes.string.isRequired,
data: PropTypes.any,
hideRoot: PropTypes.bool.isRequired,
createItemString: PropTypes.func.isRequired,
styling: PropTypes.func.isRequired,
collectionLimit: PropTypes.number,
keyPath: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
).isRequired,
labelRenderer: PropTypes.func.isRequired,
shouldExpandNode: PropTypes.func,
level: PropTypes.number.isRequired,
sortObjectKeys: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
isCircular: PropTypes.bool,
expandable: PropTypes.bool,
};
static defaultProps = { const renderedChildren =
data: [], expanded || (hideRoot && level === 0)
circularCache: [], ? renderChildNodes({ ...props, circularCache, level: level + 1 })
level: 0, : null;
expandable: true,
};
constructor(props: Props) { const itemType = (
super(props); <span {...styling('nestedNodeItemType', expanded)}>
this.state = getStateFromProps(props); {nodeTypeIndicator}
} </span>
);
const renderedItemString = getItemString(
nodeType,
data,
itemType,
createItemString(data, collectionLimit),
keyPath
);
const stylingArgs = [keyPath, nodeType, expanded, expandable] as const;
UNSAFE_componentWillReceiveProps(nextProps: Props) { return hideRoot ? (
const nextState = getStateFromProps(nextProps); <li {...styling('rootNode', ...stylingArgs)}>
if (getStateFromProps(this.props).expanded !== nextState.expanded) { <ul {...styling('rootNodeChildren', ...stylingArgs)}>
this.setState(nextState); {renderedChildren}
} </ul>
} </li>
) : (
shouldComponentUpdate(nextProps: Props, nextState: State) { <li {...styling('nestedNode', ...stylingArgs)}>
return ( {expandable && (
!!Object.keys(nextProps).find( <JSONArrow
(key) => styling={styling}
key !== 'circularCache' && nodeType={nodeType}
(key === 'keyPath' expanded={expanded}
? nextProps[key].join('/') !== this.props[key].join('/') onClick={handleClick}
: nextProps[key as keyof Props] !== this.props[key as keyof Props]) />
) || nextState.expanded !== this.state.expanded )}
); <label
} {...styling(['label', 'nestedNodeLabel'], ...stylingArgs)}
onClick={handleClick}
render() { >
const { {labelRenderer(...stylingArgs)}
getItemString, </label>
nodeTypeIndicator, <span
nodeType, {...styling('nestedNodeItemString', ...stylingArgs)}
data, onClick={handleClick}
hideRoot, >
createItemString, {renderedItemString}
styling,
collectionLimit,
keyPath,
labelRenderer,
expandable,
} = this.props;
const { expanded } = this.state;
const renderedChildren =
expanded || (hideRoot && this.props.level === 0)
? renderChildNodes({ ...this.props, level: this.props.level + 1 })
: null;
const itemType = (
<span {...styling('nestedNodeItemType', expanded)}>
{nodeTypeIndicator}
</span> </span>
); <ul {...styling('nestedNodeChildren', ...stylingArgs)}>
const renderedItemString = getItemString( {renderedChildren}
nodeType, </ul>
data, </li>
itemType, );
createItemString(data, collectionLimit),
keyPath
);
const stylingArgs = [keyPath, nodeType, expanded, expandable] as const;
return hideRoot ? (
<li {...styling('rootNode', ...stylingArgs)}>
<ul {...styling('rootNodeChildren', ...stylingArgs)}>
{renderedChildren}
</ul>
</li>
) : (
<li {...styling('nestedNode', ...stylingArgs)}>
{expandable && (
<JSONArrow
styling={styling}
nodeType={nodeType}
expanded={expanded}
onClick={this.handleClick}
/>
)}
<label
{...styling(['label', 'nestedNodeLabel'], ...stylingArgs)}
onClick={this.handleClick}
>
{labelRenderer(...stylingArgs)}
</label>
<span
{...styling('nestedNodeItemString', ...stylingArgs)}
onClick={this.handleClick}
>
{renderedItemString}
</span>
<ul {...styling('nestedNodeChildren', ...stylingArgs)}>
{renderedChildren}
</ul>
</li>
);
}
handleClick = () => {
if (this.props.expandable) {
this.setState({ expanded: !this.state.expanded });
}
};
} }

View File

@ -1,19 +1,16 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import objType from './objType'; import objType from './objType';
import JSONObjectNode from './JSONObjectNode'; import JSONObjectNode from './JSONObjectNode';
import JSONArrayNode from './JSONArrayNode'; import JSONArrayNode from './JSONArrayNode';
import JSONIterableNode from './JSONIterableNode'; import JSONIterableNode from './JSONIterableNode';
import JSONValueNode from './JSONValueNode'; import JSONValueNode from './JSONValueNode';
import { CircularPropsPassedThroughJSONNode } from './types'; import type { CommonInternalProps } from './types';
interface Props extends CircularPropsPassedThroughJSONNode { interface Props extends CommonInternalProps {
keyPath: (string | number)[]; value: unknown;
value: any;
isCustomNode: (value: any) => boolean;
} }
const JSONNode: React.FunctionComponent<Props> = ({ export default function JSONNode({
getItemString, getItemString,
keyPath, keyPath,
labelRenderer, labelRenderer,
@ -22,7 +19,7 @@ const JSONNode: React.FunctionComponent<Props> = ({
valueRenderer, valueRenderer,
isCustomNode, isCustomNode,
...rest ...rest
}) => { }: Props) {
const nodeType = isCustomNode(value) ? 'Custom' : objType(value); const nodeType = isCustomNode(value) ? 'Custom' : objType(value);
const simpleNodeProps = { const simpleNodeProps = {
@ -102,18 +99,4 @@ const JSONNode: React.FunctionComponent<Props> = ({
/> />
); );
} }
}; }
JSONNode.propTypes = {
getItemString: PropTypes.func.isRequired,
keyPath: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired
).isRequired,
labelRenderer: PropTypes.func.isRequired,
styling: PropTypes.func.isRequired,
value: PropTypes.any,
valueRenderer: PropTypes.func.isRequired,
isCustomNode: PropTypes.func.isRequired,
};
export default JSONNode;

View File

@ -1,35 +1,29 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import JSONNestedNode from './JSONNestedNode'; import JSONNestedNode from './JSONNestedNode';
import { CircularPropsPassedThroughJSONNode } from './types'; import type { CommonInternalProps } from './types';
// Returns the "n Items" string for this node, // Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet. // generating and caching it if it hasn't been created yet.
function createItemString(data: any) { function createItemString(data: unknown) {
const len = Object.getOwnPropertyNames(data).length; const len = Object.getOwnPropertyNames(data).length;
return `${len} ${len !== 1 ? 'keys' : 'key'}`; return `${len} ${len !== 1 ? 'keys' : 'key'}`;
} }
interface Props extends CircularPropsPassedThroughJSONNode { interface Props extends CommonInternalProps {
data: any; data: unknown;
nodeType: string; nodeType: string;
} }
// Configures <JSONNestedNode> to render an Object // Configures <JSONNestedNode> to render an Object
const JSONObjectNode: React.FunctionComponent<Props> = ({ data, ...props }) => ( export default function JSONObjectNode({ data, ...props }: Props) {
<JSONNestedNode return (
{...props} <JSONNestedNode
data={data} {...props}
nodeType="Object" data={data}
nodeTypeIndicator={props.nodeType === 'Error' ? 'Error()' : '{}'} nodeType="Object"
createItemString={createItemString} nodeTypeIndicator={props.nodeType === 'Error' ? 'Error()' : '{}'}
expandable={Object.getOwnPropertyNames(data).length > 0} createItemString={createItemString}
/> expandable={Object.getOwnPropertyNames(data).length > 0}
); />
);
JSONObjectNode.propTypes = { }
data: PropTypes.object,
nodeType: PropTypes.string.isRequired,
};
export default JSONObjectNode;

View File

@ -1,18 +1,30 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import type {
import { JSONValueNodeCircularPropsProvidedByJSONNode } from './types'; GetItemString,
Key,
KeyPath,
LabelRenderer,
Styling,
ValueRenderer,
} from './types';
/** /**
* Renders simple values (eg. strings, numbers, booleans, etc) * Renders simple values (eg. strings, numbers, booleans, etc)
*/ */
interface Props extends JSONValueNodeCircularPropsProvidedByJSONNode { interface Props {
getItemString: GetItemString;
key: Key;
keyPath: KeyPath;
labelRenderer: LabelRenderer;
nodeType: string; nodeType: string;
value: any; styling: Styling;
valueGetter?: (value: any) => any; value: unknown;
valueRenderer: ValueRenderer;
valueGetter?: (value: any) => unknown;
} }
const JSONValueNode: React.FunctionComponent<Props> = ({ export default function JSONValueNode({
nodeType, nodeType,
styling, styling,
labelRenderer, labelRenderer,
@ -20,27 +32,15 @@ const JSONValueNode: React.FunctionComponent<Props> = ({
valueRenderer, valueRenderer,
value, value,
valueGetter = (value) => value, valueGetter = (value) => value,
}) => ( }: Props) {
<li {...styling('value', nodeType, keyPath)}> return (
<label {...styling(['label', 'valueLabel'], nodeType, keyPath)}> <li {...styling('value', nodeType, keyPath)}>
{labelRenderer(keyPath, nodeType, false, false)} <label {...styling(['label', 'valueLabel'], nodeType, keyPath)}>
</label> {labelRenderer(keyPath, nodeType, false, false)}
<span {...styling('valueText', nodeType, keyPath)}> </label>
{valueRenderer(valueGetter(value), value, ...keyPath)} <span {...styling('valueText', nodeType, keyPath)}>
</span> {valueRenderer(valueGetter(value), value, ...keyPath)}
</li> </span>
); </li>
);
JSONValueNode.propTypes = { }
nodeType: PropTypes.string.isRequired,
styling: PropTypes.func.isRequired,
labelRenderer: PropTypes.func.isRequired,
keyPath: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired
).isRequired,
valueRenderer: PropTypes.func.isRequired,
value: PropTypes.any,
valueGetter: PropTypes.func,
};
export default JSONValueNode;

View File

@ -1,11 +1,12 @@
import type { CurriedFunction1 } from 'lodash'; import type { CurriedFunction1 } from 'lodash';
import { import { createStyling } from 'react-base16-styling';
import type {
Base16Theme, Base16Theme,
createStyling,
StylingConfig, StylingConfig,
StylingFunction,
Theme,
} from 'react-base16-styling'; } from 'react-base16-styling';
import solarized from './themes/solarized'; import solarized from './themes/solarized';
import { StylingFunction, Theme } from 'react-base16-styling/src';
const colorMap = (theme: Base16Theme) => ({ const colorMap = (theme: Base16Theme) => ({
BACKGROUND_COLOR: theme.base00, BACKGROUND_COLOR: theme.base00,

View File

@ -1,4 +1,6 @@
function getLength(type: string, collection: any) { import type { SortObjectKeys } from './types';
function getLength(type: string, collection: unknown) {
if (type === 'Object') { if (type === 'Object') {
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
return Object.keys(collection as {}).length; return Object.keys(collection as {}).length;
@ -9,17 +11,17 @@ function getLength(type: string, collection: any) {
return Infinity; return Infinity;
} }
function isIterableMap(collection: any) { function isIterableMap(collection: unknown) {
return typeof (collection as Map<any, any>).set === 'function'; return typeof (collection as Map<unknown, unknown>).set === 'function';
} }
function getEntries( function getEntries(
type: string, type: string,
collection: any, collection: any,
sortObjectKeys?: ((a: any, b: any) => number) | boolean | undefined, sortObjectKeys: SortObjectKeys,
from = 0, from = 0,
to = Infinity to = Infinity
): { entries: { key: string | number; value: any }[]; hasMore?: boolean } { ): { entries: { key: string | number; value: unknown }[]; hasMore?: boolean } {
let res; let res;
if (type === 'Object') { if (type === 'Object') {
@ -95,8 +97,8 @@ function getRanges(from: number, to: number, limit: number) {
export default function getCollectionEntries( export default function getCollectionEntries(
type: string, type: string,
collection: any, collection: unknown,
sortObjectKeys: ((a: any, b: any) => number) | boolean | undefined, sortObjectKeys: SortObjectKeys,
limit: number, limit: number,
from = 0, from = 0,
to = Infinity to = Infinity

View File

@ -3,177 +3,87 @@
// Dave Vedder <veddermatic@gmail.com> http://www.eskimospy.com/ // Dave Vedder <veddermatic@gmail.com> http://www.eskimospy.com/
// port by Daniele Zannotti http://www.github.com/dzannotti <dzannotti@me.com> // port by Daniele Zannotti http://www.github.com/dzannotti <dzannotti@me.com>
import React from 'react'; import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import JSONNode from './JSONNode'; import JSONNode from './JSONNode';
import createStylingFromTheme from './createStylingFromTheme'; import createStylingFromTheme from './createStylingFromTheme';
import { invertTheme } from 'react-base16-styling'; import { invertTheme } from 'react-base16-styling';
import type { StylingValue, Theme } from 'react-base16-styling';
import type { import type {
StylingConfig, CommonExternalProps,
StylingFunction, GetItemString,
StylingValue, IsCustomNode,
Theme, LabelRenderer,
} from 'react-base16-styling'; ShouldExpandNodeInitially,
import { CircularPropsPassedThroughJSONTree } from './types'; } from './types';
interface Props extends CircularPropsPassedThroughJSONTree { interface Props extends Partial<CommonExternalProps> {
data: any; data: unknown;
theme?: Theme; theme?: Theme;
invertTheme: boolean; invertTheme?: boolean;
}
interface State {
styling: StylingFunction;
} }
const identity = (value: any) => value; const identity = (value: any) => value;
const expandRootNode = ( const expandRootNode: ShouldExpandNodeInitially = (keyPath, data, level) =>
keyPath: (string | number)[], level === 0;
data: any, const defaultItemString: GetItemString = (type, data, itemType, itemString) => (
level: number
) => level === 0;
const defaultItemString = (
type: string,
data: any,
itemType: React.ReactNode,
itemString: string
) => (
<span> <span>
{itemType} {itemString} {itemType} {itemString}
</span> </span>
); );
const defaultLabelRenderer = ([label]: (string | number)[]) => ( const defaultLabelRenderer: LabelRenderer = ([label]) => <span>{label}:</span>;
<span>{label}:</span> const noCustomNode: IsCustomNode = () => false;
);
const noCustomNode = () => false;
function checkLegacyTheming(theme: Theme | undefined, props: Props) { export function JSONTree({
const deprecatedStylingMethodsMap = { data: value,
getArrowStyle: 'arrow', theme,
getListStyle: 'nestedNodeChildren', invertTheme: shouldInvertTheme,
getItemStringStyle: 'nestedNodeItemString', keyPath = ['root'],
getLabelStyle: 'label', labelRenderer = defaultLabelRenderer,
getValueStyle: 'valueText', valueRenderer = identity,
}; shouldExpandNodeInitially = expandRootNode,
hideRoot = false,
getItemString = defaultItemString,
postprocessValue = identity,
isCustomNode = noCustomNode,
collectionLimit = 50,
sortObjectKeys = false,
}: Props) {
const styling = useMemo(
() =>
createStylingFromTheme(shouldInvertTheme ? invertTheme(theme) : theme),
[theme, shouldInvertTheme]
);
const deprecatedStylingMethods = Object.keys( return (
deprecatedStylingMethodsMap <ul {...styling('tree')}>
).filter((name) => props[name as keyof Props]); <JSONNode
keyPath={hideRoot ? [] : keyPath}
if (deprecatedStylingMethods.length > 0) { value={postprocessValue(value)}
if (typeof theme === 'string') { isCustomNode={isCustomNode}
theme = { styling={styling}
extend: theme, labelRenderer={labelRenderer}
}; valueRenderer={valueRenderer}
} else { shouldExpandNodeInitially={shouldExpandNodeInitially}
theme = { ...theme }; hideRoot={hideRoot}
} getItemString={getItemString}
postprocessValue={postprocessValue}
deprecatedStylingMethods.forEach((name) => { collectionLimit={collectionLimit}
// eslint-disable-next-line no-console sortObjectKeys={sortObjectKeys}
console.error( />
`Styling method "${name}" is deprecated, use "theme" property instead` </ul>
); );
(theme as StylingConfig)[
deprecatedStylingMethodsMap[
name as keyof typeof deprecatedStylingMethodsMap
]
] = ({ style }, ...args) => ({
style: {
...style,
...props[name as keyof Props](...args),
},
});
});
}
return theme;
} }
function getStateFromProps(props: Props) { export type {
let theme = checkLegacyTheming(props.theme, props); Key,
if (props.invertTheme) { KeyPath,
theme = invertTheme(theme); GetItemString,
} LabelRenderer,
ValueRenderer,
return { ShouldExpandNodeInitially,
styling: createStylingFromTheme(theme), PostprocessValue,
}; IsCustomNode,
} SortObjectKeys,
Styling,
export class JSONTree extends React.Component<Props, State> { } from './types';
static propTypes = { export type { StylingValue };
data: PropTypes.any,
hideRoot: PropTypes.bool,
theme: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
invertTheme: PropTypes.bool,
keyPath: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
),
postprocessValue: PropTypes.func,
sortObjectKeys: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
};
static defaultProps = {
shouldExpandNode: expandRootNode,
hideRoot: false,
keyPath: ['root'],
getItemString: defaultItemString,
labelRenderer: defaultLabelRenderer,
valueRenderer: identity,
postprocessValue: identity,
isCustomNode: noCustomNode,
collectionLimit: 50,
invertTheme: true,
};
constructor(props: Props) {
super(props);
this.state = getStateFromProps(props);
}
UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (
['theme', 'invertTheme'].find(
(k) => nextProps[k as keyof Props] !== this.props[k as keyof Props]
)
) {
this.setState(getStateFromProps(nextProps));
}
}
shouldComponentUpdate(nextProps: Props) {
return !!Object.keys(nextProps).find((k) =>
k === 'keyPath'
? nextProps[k].join('/') !== this.props[k].join('/')
: nextProps[k as keyof Props] !== this.props[k as keyof Props]
);
}
render() {
const {
data: value,
keyPath,
postprocessValue,
hideRoot,
theme, // eslint-disable-line no-unused-vars
invertTheme: _, // eslint-disable-line no-unused-vars
...rest
} = this.props;
const { styling } = this.state;
return (
<ul {...styling('tree')}>
<JSONNode
{...{ postprocessValue, hideRoot, styling, ...rest }}
keyPath={hideRoot ? [] : keyPath}
value={postprocessValue(value)}
/>
</ul>
);
}
}
export { StylingValue };

View File

@ -1,81 +1,63 @@
import React from 'react'; import React from 'react';
import { StylingFunction } from 'react-base16-styling'; import { StylingFunction } from 'react-base16-styling';
interface SharedCircularPropsPassedThroughJSONTree { export type Key = string | number;
keyPath: (string | number)[];
labelRenderer: (
keyPath: (string | number)[],
nodeType: string,
expanded: boolean,
expandable: boolean
) => React.ReactNode;
}
interface SharedCircularPropsProvidedByJSONTree
extends SharedCircularPropsPassedThroughJSONTree {
styling: StylingFunction;
}
interface JSONValueNodeCircularPropsPassedThroughJSONTree {
valueRenderer: (
valueAsString: any,
value: any,
...keyPath: (string | number)[]
) => React.ReactNode;
}
export type JSONValueNodeCircularPropsProvidedByJSONNode =
SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree;
interface JSONNestedNodeCircularPropsPassedThroughJSONTree { export type KeyPath = readonly (string | number)[];
shouldExpandNode: (
keyPath: (string | number)[], export type GetItemString = (
data: any, nodeType: string,
level: number data: unknown,
) => boolean; itemType: React.ReactNode,
itemString: string,
keyPath: KeyPath
) => React.ReactNode;
export type LabelRenderer = (
keyPath: KeyPath,
nodeType: string,
expanded: boolean,
expandable: boolean
) => React.ReactNode;
export type ValueRenderer = (
valueAsString: unknown,
value: unknown,
...keyPath: KeyPath
) => React.ReactNode;
export type ShouldExpandNodeInitially = (
keyPath: KeyPath,
data: unknown,
level: number
) => boolean;
export type PostprocessValue = (value: unknown) => unknown;
export type IsCustomNode = (value: unknown) => boolean;
export type SortObjectKeys = ((a: unknown, b: unknown) => number) | boolean;
export type Styling = StylingFunction;
export type CircularCache = unknown[];
export interface CommonExternalProps {
keyPath: KeyPath;
labelRenderer: LabelRenderer;
valueRenderer: ValueRenderer;
shouldExpandNodeInitially: ShouldExpandNodeInitially;
hideRoot: boolean; hideRoot: boolean;
getItemString: ( getItemString: GetItemString;
nodeType: string, postprocessValue: PostprocessValue;
data: any, isCustomNode: IsCustomNode;
itemType: React.ReactNode,
itemString: string,
keyPath: (string | number)[]
) => React.ReactNode;
postprocessValue: (value: any) => any;
isCustomNode: (value: any) => boolean;
collectionLimit: number; collectionLimit: number;
sortObjectKeys?: ((a: any, b: any) => number) | boolean; sortObjectKeys: SortObjectKeys;
} }
export type CircularPropsPassedThroughJSONTree =
SharedCircularPropsPassedThroughJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONTree;
interface JSONNestedNodeCircularPropsPassedThroughJSONNode export interface CommonInternalProps extends CommonExternalProps {
extends JSONNestedNodeCircularPropsPassedThroughJSONTree { styling: StylingFunction;
circularCache?: any[]; circularCache?: CircularCache;
isCircular?: boolean;
level?: number; level?: number;
isCircular?: boolean;
} }
export type CircularPropsPassedThroughJSONNode =
SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNode;
export interface JSONNestedNodeCircularPropsPassedThroughJSONNestedNode
extends JSONNestedNodeCircularPropsPassedThroughJSONNode {
circularCache: any[];
level: number;
}
export type CircularPropsPassedThroughJSONNestedNode =
SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNestedNode;
export type CircularPropsPassedThroughRenderChildNodes =
SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNestedNode;
export type CircularPropsPassedThroughItemRange =
SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNestedNode;

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { Base16Theme } from 'redux-devtools-themes'; import { Base16Theme } from 'redux-devtools-themes';
import { Action } from 'redux'; import { Action } from 'redux';
import type { StylingFunction } from 'react-base16-styling'; import type { StylingFunction } from 'react-base16-styling';
import type { LabelRenderer } from 'react-json-tree';
import { PerformAction } from '@redux-devtools/core'; import { PerformAction } from '@redux-devtools/core';
import { Delta } from 'jsondiffpatch'; import { Delta } from 'jsondiffpatch';
import { DEFAULT_STATE, DevtoolsInspectorState } from './redux'; import { DEFAULT_STATE, DevtoolsInspectorState } from './redux';
@ -11,12 +12,7 @@ import StateTab from './tabs/StateTab';
import ActionTab from './tabs/ActionTab'; import ActionTab from './tabs/ActionTab';
export interface TabComponentProps<S, A extends Action<unknown>> { export interface TabComponentProps<S, A extends Action<unknown>> {
labelRenderer: ( labelRenderer: LabelRenderer;
keyPath: (string | number)[],
nodeType: string,
expanded: boolean,
expandable: boolean
) => React.ReactNode;
styling: StylingFunction; styling: StylingFunction;
computedStates: { state: S; error?: string }[]; computedStates: { state: S; error?: string }[];
actions: { [actionId: number]: PerformAction<A> }; actions: { [actionId: number]: PerformAction<A> };
@ -152,11 +148,7 @@ class ActionPreview<S, A extends Action<unknown>> extends Component<
); );
} }
labelRenderer = ( labelRenderer: LabelRenderer = ([key, ...rest], nodeType, expanded) => {
[key, ...rest]: (string | number)[],
nodeType: string,
expanded: boolean
) => {
const { styling, onInspectPath, inspectedPath } = this.props; const { styling, onInspectPath, inspectedPath } = this.props;
return ( return (

View File

@ -1,4 +1,5 @@
export type { StylingFunction } from 'react-base16-styling'; export type { StylingFunction } from 'react-base16-styling';
export type { LabelRenderer } from 'react-json-tree';
export { default as InspectorMonitor } from './DevtoolsInspector'; export { default as InspectorMonitor } from './DevtoolsInspector';
export type { Tab, TabComponentProps } from './ActionPreview'; export type { Tab, TabComponentProps } from './ActionPreview';
export type { DevtoolsInspectorState } from './redux'; export type { DevtoolsInspectorState } from './redux';

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { JSONTree } from 'react-json-tree'; import { JSONTree } from 'react-json-tree';
import type { LabelRenderer, ShouldExpandNodeInitially } from 'react-json-tree';
import { stringify } from 'javascript-stringify'; import { stringify } from 'javascript-stringify';
import { Delta } from 'jsondiffpatch'; import { Delta } from 'jsondiffpatch';
import { StylingFunction } from 'react-base16-styling'; import { StylingFunction } from 'react-base16-styling';
@ -22,11 +23,8 @@ function stringifyAndShrink(val: any, isWideLayout?: boolean) {
return str.length > 22 ? `${str.substr(0, 15)}${str.substr(-5)}` : str; return str.length > 22 ? `${str.substr(0, 15)}${str.substr(-5)}` : str;
} }
const expandFirstLevel = ( const expandFirstLevel: ShouldExpandNodeInitially = (keyName, data, level) =>
keyName: (string | number)[], level <= 1;
data: any,
level: number
) => level <= 1;
function prepareDelta(value: any) { function prepareDelta(value: any) {
if (value && value._t === 'a') { if (value && value._t === 'a') {
@ -53,12 +51,7 @@ interface Props {
styling: StylingFunction; styling: StylingFunction;
base16Theme: Base16Theme; base16Theme: Base16Theme;
invertTheme: boolean; invertTheme: boolean;
labelRenderer: ( labelRenderer: LabelRenderer;
keyPath: (string | number)[],
nodeType: string,
expanded: boolean,
expandable: boolean
) => React.ReactNode;
isWideLayout: boolean; isWideLayout: boolean;
dataTypeKey: string | symbol | undefined; dataTypeKey: string | symbol | undefined;
} }
@ -104,7 +97,7 @@ export default class JSONDiff extends Component<Props, State> {
valueRenderer={this.valueRenderer} valueRenderer={this.valueRenderer}
postprocessValue={prepareDelta} postprocessValue={prepareDelta}
isCustomNode={Array.isArray} isCustomNode={Array.isArray}
shouldExpandNode={expandFirstLevel} shouldExpandNodeInitially={expandFirstLevel}
hideRoot hideRoot
/> />
); );

View File

@ -1,6 +1,7 @@
import React, { CSSProperties, MouseEventHandler, PureComponent } from 'react'; import React, { CSSProperties, MouseEventHandler, PureComponent } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { JSONTree, StylingValue } from 'react-json-tree'; import { JSONTree } from 'react-json-tree';
import type { ShouldExpandNodeInitially, StylingValue } from 'react-json-tree';
import { Base16Theme } from 'redux-devtools-themes'; import { Base16Theme } from 'redux-devtools-themes';
import { Action } from 'redux'; import { Action } from 'redux';
import LogMonitorEntryAction from './LogMonitorEntryAction'; import LogMonitorEntryAction from './LogMonitorEntryAction';
@ -115,7 +116,7 @@ export default class LogMonitorEntry<
data={data} data={data}
invertTheme={false} invertTheme={false}
keyPath={['state']} keyPath={['state']}
shouldExpandNode={this.shouldExpandNode} shouldExpandNodeInitially={this.shouldExpandNodeInitially}
/> />
); );
} catch (err) { } catch (err) {
@ -149,10 +150,10 @@ export default class LogMonitorEntry<
} }
}; };
shouldExpandNode = ( shouldExpandNodeInitially: ShouldExpandNodeInitially = (
keyPath: (string | number)[], keyPath,
data: any, data,
level: number level
) => { ) => {
return this.props.expandStateRoot && level === 0; return this.props.expandStateRoot && level === 0;
}; };

View File

@ -1,5 +1,6 @@
import React, { Component, CSSProperties, MouseEventHandler } from 'react'; import React, { Component, CSSProperties, MouseEventHandler } from 'react';
import { JSONTree } from 'react-json-tree'; import { JSONTree } from 'react-json-tree';
import type { ShouldExpandNodeInitially } from 'react-json-tree';
import { Base16Theme } from 'redux-devtools-themes'; import { Base16Theme } from 'redux-devtools-themes';
import { Action } from 'redux'; import { Action } from 'redux';
@ -42,7 +43,7 @@ export default class LogMonitorAction<
invertTheme={false} invertTheme={false}
keyPath={['action']} keyPath={['action']}
data={payload} data={payload}
shouldExpandNode={this.shouldExpandNode} shouldExpandNodeInitially={this.shouldExpandNodeInitially}
/> />
) : ( ) : (
'' ''
@ -51,10 +52,10 @@ export default class LogMonitorAction<
); );
} }
shouldExpandNode = ( shouldExpandNodeInitially: ShouldExpandNodeInitially = (
keyPath: (string | number)[], keyPath,
data: any, data,
level: number level
) => { ) => {
return this.props.expandActionRoot && level === 0; return this.props.expandActionRoot && level === 0;
}; };

View File

@ -1,6 +1,7 @@
import { createSelector, Selector } from '@reduxjs/toolkit'; import { createSelector, Selector } from '@reduxjs/toolkit';
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import { Action, AnyAction } from 'redux'; import { Action, AnyAction } from 'redux';
import type { KeyPath, ShouldExpandNodeInitially } from 'react-json-tree';
import { QueryPreviewTabs } from '../types'; import { QueryPreviewTabs } from '../types';
import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y'; import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
import { emptyRecord, identity } from '../utils/object'; import { emptyRecord, identity } from '../utils/object';
@ -43,7 +44,7 @@ export class QueryPreviewActions extends PureComponent<QueryPreviewActionsProps>
return output; return output;
}); });
isLastActionNode = (keyPath: (string | number)[], layer: number): boolean => { isLastActionNode = (keyPath: KeyPath, layer: number): boolean => {
if (layer >= 1) { if (layer >= 1) {
const len = this.props.actionsOfQuery.length; const len = this.props.actionsOfQuery.length;
const actionKey = keyPath[keyPath.length - 1]; const actionKey = keyPath[keyPath.length - 1];
@ -58,11 +59,11 @@ export class QueryPreviewActions extends PureComponent<QueryPreviewActionsProps>
return false; return false;
}; };
shouldExpandNode = ( shouldExpandNodeInitially: ShouldExpandNodeInitially = (
keyPath: (string | number)[], keyPath,
value: unknown, value,
layer: number layer
): boolean => { ) => {
if (layer === 1) { if (layer === 1) {
return this.isLastActionNode(keyPath, layer); return this.isLastActionNode(keyPath, layer);
} }
@ -85,7 +86,7 @@ export class QueryPreviewActions extends PureComponent<QueryPreviewActionsProps>
rootProps={rootProps} rootProps={rootProps}
data={this.selectFormattedActions(actionsOfQuery)} data={this.selectFormattedActions(actionsOfQuery)}
isWideLayout={isWideLayout} isWideLayout={isWideLayout}
shouldExpandNode={this.shouldExpandNode} shouldExpandNodeInitially={this.shouldExpandNodeInitially}
/> />
); );
} }

View File

@ -1,4 +1,5 @@
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import type { ShouldExpandNodeInitially } from 'react-json-tree';
import { ApiStats, QueryPreviewTabs, RtkQueryApiState } from '../types'; import { ApiStats, QueryPreviewTabs, RtkQueryApiState } from '../types';
import { StyleUtilsContext } from '../styles/createStylingFromTheme'; import { StyleUtilsContext } from '../styles/createStylingFromTheme';
import { TreeView, TreeViewProps } from './TreeView'; import { TreeView, TreeViewProps } from './TreeView';
@ -17,11 +18,11 @@ const rootProps: TreeViewProps['rootProps'] = {
}; };
export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> { export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> {
shouldExpandApiStateNode = ( shouldExpandApiStateNode: ShouldExpandNodeInitially = (
keyPath: (string | number)[], keyPath,
value: unknown, value,
layer: number layer
): boolean => { ) => {
const lastKey = keyPath[keyPath.length - 1]; const lastKey = keyPath[keyPath.length - 1];
return layer <= 1 && lastKey !== 'config'; return layer <= 1 && lastKey !== 'config';
@ -45,7 +46,7 @@ export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> {
<TreeView <TreeView
before={<h3>State</h3>} before={<h3>State</h3>}
data={apiState} data={apiState}
shouldExpandNode={this.shouldExpandApiStateNode} shouldExpandNodeInitially={this.shouldExpandApiStateNode}
isWideLayout={isWideLayout} isWideLayout={isWideLayout}
/> />
{apiStats && ( {apiStats && (

View File

@ -1,4 +1,5 @@
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import type { ShouldExpandNodeInitially } from 'react-json-tree';
import { QueryPreviewTabs, RtkResourceInfo } from '../types'; import { QueryPreviewTabs, RtkResourceInfo } from '../types';
import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y'; import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
import { TreeView, TreeViewProps } from './TreeView'; import { TreeView, TreeViewProps } from './TreeView';
@ -15,11 +16,11 @@ const rootProps: TreeViewProps['rootProps'] = {
}; };
export class QueryPreviewData extends PureComponent<QueryPreviewDataProps> { export class QueryPreviewData extends PureComponent<QueryPreviewDataProps> {
shouldExpandNode = ( shouldExpandNodeInitially: ShouldExpandNodeInitially = (
keyPath: (string | number)[], keyPath,
value: unknown, value,
layer: number layer
): boolean => { ) => {
return layer <= 1; return layer <= 1;
}; };
@ -31,7 +32,7 @@ export class QueryPreviewData extends PureComponent<QueryPreviewDataProps> {
rootProps={rootProps} rootProps={rootProps}
data={data} data={data}
isWideLayout={isWideLayout} isWideLayout={isWideLayout}
shouldExpandNode={this.shouldExpandNode} shouldExpandNodeInitially={this.shouldExpandNodeInitially}
/> />
); );
} }

View File

@ -1,6 +1,7 @@
import { createSelector, Selector } from '@reduxjs/toolkit'; import { createSelector, Selector } from '@reduxjs/toolkit';
import { QueryStatus } from '@reduxjs/toolkit/dist/query'; import { QueryStatus } from '@reduxjs/toolkit/dist/query';
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import type { ShouldExpandNodeInitially } from 'react-json-tree';
import { QueryPreviewTabs, RtkResourceInfo, RTKStatusFlags } from '../types'; import { QueryPreviewTabs, RtkResourceInfo, RTKStatusFlags } from '../types';
import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y'; import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
import { formatMs } from '../utils/formatters'; import { formatMs } from '../utils/formatters';
@ -35,11 +36,11 @@ export interface QueryPreviewInfoProps {
isWideLayout: boolean; isWideLayout: boolean;
} }
export class QueryPreviewInfo extends PureComponent<QueryPreviewInfoProps> { export class QueryPreviewInfo extends PureComponent<QueryPreviewInfoProps> {
shouldExpandNode = ( shouldExpandNodeInitially: ShouldExpandNodeInitially = (
keyPath: (string | number)[], keyPath,
value: unknown, value,
layer: number layer
): boolean => { ) => {
const lastKey = keyPath[keyPath.length - 1]; const lastKey = keyPath[keyPath.length - 1];
return layer <= 1 && lastKey !== 'query' && lastKey !== 'mutation'; return layer <= 1 && lastKey !== 'query' && lastKey !== 'mutation';
@ -107,7 +108,7 @@ export class QueryPreviewInfo extends PureComponent<QueryPreviewInfoProps> {
rootProps={rootProps} rootProps={rootProps}
data={formattedQuery} data={formattedQuery}
isWideLayout={isWideLayout} isWideLayout={isWideLayout}
shouldExpandNode={this.shouldExpandNode} shouldExpandNodeInitially={this.shouldExpandNodeInitially}
/> />
); );
} }

View File

@ -14,7 +14,7 @@ export interface TreeViewProps
extends Partial< extends Partial<
Pick< Pick<
ComponentProps<typeof JSONTree>, ComponentProps<typeof JSONTree>,
'keyPath' | 'shouldExpandNode' | 'hideRoot' 'keyPath' | 'shouldExpandNodeInitially' | 'hideRoot'
> >
> { > {
data: unknown; data: unknown;
@ -30,7 +30,7 @@ export interface TreeViewProps
export class TreeView extends React.PureComponent<TreeViewProps> { export class TreeView extends React.PureComponent<TreeViewProps> {
static defaultProps = { static defaultProps = {
hideRoot: true, hideRoot: true,
shouldExpandNode: ( shouldExpandNodeInitially: (
keyPath: (string | number)[], keyPath: (string | number)[],
value: unknown, value: unknown,
layer: number layer: number
@ -81,7 +81,7 @@ export class TreeView extends React.PureComponent<TreeViewProps> {
after, after,
children, children,
keyPath, keyPath,
shouldExpandNode, shouldExpandNodeInitially,
hideRoot, hideRoot,
rootProps, rootProps,
} = this.props; } = this.props;
@ -94,7 +94,7 @@ export class TreeView extends React.PureComponent<TreeViewProps> {
{before} {before}
<JSONTree <JSONTree
keyPath={keyPath} keyPath={keyPath}
shouldExpandNode={shouldExpandNode} shouldExpandNodeInitially={shouldExpandNodeInitially}
data={data} data={data}
labelRenderer={this.selectLabelRenderer(styling)} labelRenderer={this.selectLabelRenderer(styling)}
theme={this.selectTheme(base16Theme)} theme={this.selectTheme(base16Theme)}

View File

@ -1,5 +1,6 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { StylingFunction } from 'react-base16-styling'; import { StylingFunction } from 'react-base16-styling';
import type { LabelRenderer } from 'react-json-tree';
import { isCollection, isIndexed, isKeyed } from 'immutable'; import { isCollection, isIndexed, isKeyed } from 'immutable';
import isIterable from '../utils/isIterable'; import isIterable from '../utils/isIterable';
@ -91,12 +92,10 @@ export function getItemString(
); );
} }
export function createTreeItemLabelRenderer(styling: StylingFunction) { export function createTreeItemLabelRenderer(
return function labelRenderer( styling: StylingFunction
[key]: (string | number)[], ): LabelRenderer {
nodeType: string, return function labelRenderer([key], nodeType, expanded) {
expanded: boolean
): ReactNode {
return ( return (
<span> <span>
<span {...styling('treeItemKey')}>{key}</span> <span {...styling('treeItemKey')}>{key}</span>

View File

@ -536,7 +536,6 @@ importers:
'@types/jest': ^29.2.4 '@types/jest': ^29.2.4
'@types/lodash': ^4.14.191 '@types/lodash': ^4.14.191
'@types/node': ^18.11.17 '@types/node': ^18.11.17
'@types/prop-types': ^15.7.5
'@types/react': ^18.0.26 '@types/react': ^18.0.26
'@types/react-test-renderer': ^18.0.0 '@types/react-test-renderer': ^18.0.0
'@typescript-eslint/eslint-plugin': ^5.47.0 '@typescript-eslint/eslint-plugin': ^5.47.0
@ -547,7 +546,6 @@ importers:
eslint-plugin-react: ^7.31.11 eslint-plugin-react: ^7.31.11
eslint-plugin-react-hooks: ^4.6.0 eslint-plugin-react-hooks: ^4.6.0
jest: ^29.3.1 jest: ^29.3.1
prop-types: ^15.8.1
react: ^18.2.0 react: ^18.2.0
react-base16-styling: ^0.9.1 react-base16-styling: ^0.9.1
react-test-renderer: ^18.2.0 react-test-renderer: ^18.2.0
@ -560,8 +558,6 @@ importers:
dependencies: dependencies:
'@babel/runtime': 7.20.6 '@babel/runtime': 7.20.6
'@types/lodash': 4.14.191 '@types/lodash': 4.14.191
'@types/prop-types': 15.7.5
prop-types: 15.8.1
react-base16-styling: link:../react-base16-styling react-base16-styling: link:../react-base16-styling
devDependencies: devDependencies:
'@babel/cli': 7.19.3_@babel+core@7.20.5 '@babel/cli': 7.19.3_@babel+core@7.20.5