-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Remove UNSAFE_componentWillReceiveProps from react-json-tree #1049
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
Changes from all commits
f70f183
68dba97
9a8a6e9
6b4da1b
9c00bdd
ed9cb23
03a8af7
ffcbe5d
043682d
cf66127
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import React from 'react'; | ||
import React, { useCallback, useEffect, useRef, useState } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import JSONArrow from './JSONArrow'; | ||
import getCollectionEntries from './getCollectionEntries'; | ||
|
@@ -42,7 +42,7 @@ function renderChildNodes( | |
nodeType, | ||
data, | ||
collectionLimit, | ||
circularCache, | ||
circularCache = [], | ||
keyPath, | ||
postprocessValue, | ||
sortObjectKeys, | ||
|
@@ -94,147 +94,116 @@ interface Props extends CircularPropsPassedThroughJSONNestedNode { | |
nodeType: string; | ||
nodeTypeIndicator: string; | ||
createItemString: (data: any, collectionLimit: number) => string; | ||
expandable: boolean; | ||
expandable?: boolean; | ||
} | ||
|
||
interface State { | ||
expanded: boolean; | ||
} | ||
export default function JSONNestedNode(props: Props) { | ||
const { | ||
getItemString, | ||
nodeTypeIndicator, | ||
nodeType, | ||
data = [], | ||
hideRoot, | ||
createItemString, | ||
styling, | ||
collectionLimit, | ||
keyPath, | ||
labelRenderer, | ||
expandable = true, | ||
isCircular, | ||
level = 0, | ||
shouldExpandNode, | ||
} = props; | ||
|
||
function getStateFromProps(props: Props) { | ||
// calculate individual node expansion if necessary | ||
const expanded = !props.isCircular | ||
? props.shouldExpandNode(props.keyPath, props.data, props.level) | ||
: false; | ||
return { | ||
expanded, | ||
}; | ||
} | ||
const [expanded, setExpanded] = useState<boolean>(() => { | ||
return !isCircular ? shouldExpandNode(keyPath, data, level) : false; | ||
}); | ||
|
||
export default class JSONNestedNode extends React.Component<Props, State> { | ||
static propTypes = { | ||
getItemString: PropTypes.func.isRequired, | ||
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 = { | ||
data: [], | ||
circularCache: [], | ||
level: 0, | ||
expandable: true, | ||
}; | ||
|
||
constructor(props: Props) { | ||
super(props); | ||
this.state = getStateFromProps(props); | ||
} | ||
|
||
UNSAFE_componentWillReceiveProps(nextProps: Props) { | ||
const nextState = getStateFromProps(nextProps); | ||
if (getStateFromProps(this.props).expanded !== nextState.expanded) { | ||
this.setState(nextState); | ||
// When certain props change, we need to re-compute whether our node should be in an expanded state | ||
useEffect(() => { | ||
setExpanded(() => { | ||
return !isCircular ? shouldExpandNode(keyPath, data, level) : false; | ||
}); | ||
}, [isCircular, data, keyPath, level, shouldExpandNode]); | ||
Comment on lines
+122
to
+127
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this would reintroduce the undesired behavior discussed here since We're unfortunately stuck in an awkward situation where this component is partially controlled and it's unclear when |
||
|
||
const renderedChildren = | ||
expanded || (hideRoot && props.level === 0) | ||
? renderChildNodes({ ...props, level: level + 1 }) | ||
: null; | ||
|
||
const itemType = ( | ||
<span {...styling('nestedNodeItemType', expanded)}> | ||
{nodeTypeIndicator} | ||
</span> | ||
); | ||
const renderedItemString = getItemString( | ||
nodeType, | ||
data, | ||
itemType, | ||
createItemString(data, collectionLimit), | ||
keyPath | ||
); | ||
const stylingArgs = [keyPath, nodeType, expanded, expandable] as const; | ||
|
||
const expandableLatest = useRef<boolean>(expandable); | ||
expandableLatest.current = expandable; | ||
const handleClick = useCallback(() => { | ||
if (expandableLatest.current) { | ||
setExpanded((prevValue) => !prevValue); | ||
Comment on lines
+148
to
+152
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume the intention of the |
||
} | ||
} | ||
|
||
shouldComponentUpdate(nextProps: Props, nextState: State) { | ||
return ( | ||
!!Object.keys(nextProps).find( | ||
(key) => | ||
key !== 'circularCache' && | ||
(key === 'keyPath' | ||
? nextProps[key].join('/') !== this.props[key].join('/') | ||
: nextProps[key as keyof Props] !== this.props[key as keyof Props]) | ||
) || nextState.expanded !== this.state.expanded | ||
); | ||
} | ||
|
||
render() { | ||
const { | ||
getItemString, | ||
nodeTypeIndicator, | ||
nodeType, | ||
data, | ||
hideRoot, | ||
createItemString, | ||
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} | ||
}, []); | ||
|
||
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={handleClick} | ||
/> | ||
)} | ||
<label | ||
{...styling(['label', 'nestedNodeLabel'], ...stylingArgs)} | ||
onClick={handleClick} | ||
> | ||
{labelRenderer(...stylingArgs)} | ||
</label> | ||
<span | ||
{...styling('nestedNodeItemString', ...stylingArgs)} | ||
onClick={handleClick} | ||
> | ||
{renderedItemString} | ||
</span> | ||
); | ||
const renderedItemString = getItemString( | ||
nodeType, | ||
data, | ||
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 }); | ||
} | ||
}; | ||
<ul {...styling('nestedNodeChildren', ...stylingArgs)}> | ||
{renderedChildren} | ||
</ul> | ||
</li> | ||
); | ||
} | ||
|
||
JSONNestedNode.propTypes = { | ||
getItemString: PropTypes.func.isRequired, | ||
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, | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason this is passed as a function into
setExpanded
? This seems like it would be equivalent to just calculating it without a function.