Skip to content

Remove UNSAFE methods from react-json-tree #1288

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jan 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/four-parrots-poke.md
Original file line number Diff line number Diff line change
@@ -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`.
2 changes: 1 addition & 1 deletion packages/react-json-tree/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Their full signatures are:

#### 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.
- `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`
Expand Down
8 changes: 6 additions & 2 deletions packages/react-json-tree/examples/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ const App = () => (
<span role="img" aria-label="mellow">
😐
</span>{' '}
{raw}{' '}
{raw as string}{' '}
<span role="img" aria-label="mellow">
😐
</span>
Expand All @@ -194,7 +194,11 @@ const App = () => (
</div>
<p>Collapsed root node</p>
<div>
<JSONTree data={data} theme={theme} shouldExpandNode={() => false} />
<JSONTree
data={data}
theme={theme}
shouldExpandNodeInitially={() => false}
/>
</div>
</div>
);
Expand Down
6 changes: 2 additions & 4 deletions packages/react-json-tree/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@
"dependencies": {
"@babel/runtime": "^7.20.6",
"@types/lodash": "^4.14.191",
"@types/prop-types": "^15.7.5",
"prop-types": "^15.8.1",
"react-base16-styling": "^0.9.1"
},
"devDependencies": {
Expand Down Expand Up @@ -85,7 +83,7 @@
"typescript": "~4.9.4"
},
"peerDependencies": {
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0",
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
}
76 changes: 28 additions & 48 deletions packages/react-json-tree/src/ItemRange.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import React, { useCallback, useState } from 'react';
import JSONArrow from './JSONArrow';
import { CircularPropsPassedThroughItemRange } from './types';
import type { CircularCache, CommonInternalProps } from './types';

interface Props extends CircularPropsPassedThroughItemRange {
data: any;
interface Props extends CommonInternalProps {
data: unknown;
nodeType: string;
from: number;
to: number;
renderChildNodes: (props: Props, from: number, to: number) => React.ReactNode;
circularCache: CircularCache;
level: number;
}

interface State {
expanded: boolean;
}

export default class ItemRange extends React.Component<Props, State> {
static propTypes = {
styling: PropTypes.func.isRequired,
from: PropTypes.number.isRequired,
to: PropTypes.number.isRequired,
renderChildNodes: PropTypes.func.isRequired,
nodeType: PropTypes.string.isRequired,
};

constructor(props: Props) {
super(props);
this.state = { expanded: false };
}

render() {
const { styling, from, to, renderChildNodes, nodeType } = this.props;
export default function ItemRange(props: Props) {
const { styling, from, to, renderChildNodes, nodeType } = props;

return this.state.expanded ? (
<div {...styling('itemRange', this.state.expanded)}>
{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>
);
}
const [expanded, setExpanded] = useState<boolean>(false);
const handleClick = useCallback(() => {
setExpanded(!expanded);
}, [expanded]);

handleClick = () => {
this.setState({ expanded: !this.state.expanded });
};
return expanded ? (
<div {...styling('itemRange', expanded)}>
{renderChildNodes(props, from, to)}
</div>
) : (
<div {...styling('itemRange', expanded)} onClick={handleClick}>
<JSONArrow
nodeType={nodeType}
styling={styling}
expanded={false}
onClick={handleClick}
arrowStyle="double"
/>
{`${from} ... ${to}`}
</div>
);
}
37 changes: 16 additions & 21 deletions packages/react-json-tree/src/JSONArrayNode.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import JSONNestedNode from './JSONNestedNode';
import { CircularPropsPassedThroughJSONNode } from './types';
import type { CommonInternalProps } from './types';

// Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet.
function createItemString(data: any) {
function createItemString(data: unknown) {
return `${(data as unknown[]).length} ${
(data as unknown[]).length !== 1 ? 'items' : 'item'
}`;
}

interface Props extends CircularPropsPassedThroughJSONNode {
data: any;
interface Props extends CommonInternalProps {
data: unknown;
nodeType: string;
}

// Configures <JSONNestedNode> to render an Array
const JSONArrayNode: React.FunctionComponent<Props> = ({ data, ...props }) => (
<JSONNestedNode
{...props}
data={data}
nodeType="Array"
nodeTypeIndicator="[]"
createItemString={createItemString}
expandable={data.length > 0}
/>
);

JSONArrayNode.propTypes = {
data: PropTypes.array,
};

export default JSONArrayNode;
export default function JSONArrayNode({ data, ...props }: Props) {
return (
<JSONNestedNode
{...props}
data={data}
nodeType="Array"
nodeTypeIndicator="[]"
createItemString={createItemString}
expandable={(data as unknown[]).length > 0}
/>
);
}
41 changes: 14 additions & 27 deletions packages/react-json-tree/src/JSONArrow.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { StylingFunction } from 'react-base16-styling';
import type { StylingFunction } from 'react-base16-styling';

interface Props {
styling: StylingFunction;
Expand All @@ -10,33 +9,21 @@ interface Props {
onClick: React.MouseEventHandler<HTMLDivElement>;
}

const JSONArrow: React.FunctionComponent<Props> = ({
export default function JSONArrow({
styling,
arrowStyle,
arrowStyle = 'single',
expanded,
nodeType,
onClick,
}) => (
<div {...styling('arrowContainer', arrowStyle)} onClick={onClick}>
<div {...styling(['arrow', 'arrowSign'], nodeType, expanded, arrowStyle)}>
{'\u25B6'}
{arrowStyle === 'double' && (
<div {...styling(['arrowSign', 'arrowSignInner'])}>{'\u25B6'}</div>
)}
}: Props) {
return (
<div {...styling('arrowContainer', arrowStyle)} onClick={onClick}>
<div {...styling(['arrow', 'arrowSign'], nodeType, expanded, arrowStyle)}>
{'\u25B6'}
{arrowStyle === 'double' && (
<div {...styling(['arrowSign', 'arrowSignInner'])}>{'\u25B6'}</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;
);
}
13 changes: 6 additions & 7 deletions packages/react-json-tree/src/JSONIterableNode.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import JSONNestedNode from './JSONNestedNode';
import { CircularPropsPassedThroughJSONNode } from './types';
import type { CommonInternalProps } from './types';

// Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet.
Expand All @@ -22,21 +22,20 @@ function createItemString(data: any, limit: number) {
return `${hasMore ? '>' : ''}${count} ${count !== 1 ? 'entries' : 'entry'}`;
}

interface Props extends CircularPropsPassedThroughJSONNode {
data: any;
interface Props extends CommonInternalProps {
data: unknown;
nodeType: string;
}

// Configures <JSONNestedNode> to render an iterable
const JSONIterableNode: React.FunctionComponent<Props> = ({ ...props }) => {
export default function JSONIterableNode(props: Props) {
return (
<JSONNestedNode
{...props}
nodeType="Iterable"
nodeTypeIndicator="()"
createItemString={createItemString}
expandable
/>
);
};

export default JSONIterableNode;
}
Loading