Skip to content

Commit c6db35f

Browse files
committed
Remove UNSAFE_componentWillReceiveProps from JSONNestedNode
1 parent f70f183 commit c6db35f

File tree

1 file changed

+109
-137
lines changed

1 file changed

+109
-137
lines changed
Lines changed: 109 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useCallback, useEffect, useRef, useState } from 'react';
22
import PropTypes from 'prop-types';
33
import JSONArrow from './JSONArrow';
44
import getCollectionEntries from './getCollectionEntries';
@@ -97,144 +97,116 @@ interface Props extends CircularPropsPassedThroughJSONNestedNode {
9797
expandable: boolean;
9898
}
9999

100-
interface State {
101-
expanded: boolean;
102-
}
100+
function JSONNestedNode(props: Props) {
101+
const {
102+
getItemString,
103+
nodeTypeIndicator,
104+
nodeType,
105+
data = [],
106+
hideRoot,
107+
createItemString,
108+
styling,
109+
collectionLimit,
110+
keyPath,
111+
labelRenderer,
112+
expandable = true,
113+
isCircular,
114+
level = 0,
115+
shouldExpandNode,
116+
} = props;
103117

104-
function getStateFromProps(props: Props) {
105-
// calculate individual node expansion if necessary
106-
const expanded = !props.isCircular
107-
? props.shouldExpandNode(props.keyPath, props.data, props.level)
108-
: false;
109-
return {
110-
expanded,
111-
};
112-
}
118+
// fixme - call shouldExpandNode to figure out if the should be expanded to start
119+
const [expanded, setExpanded] = useState<boolean>(() => {
120+
return !isCircular ? shouldExpandNode(keyPath, data, level) : false;
121+
});
113122

114-
export default class JSONNestedNode extends React.Component<Props, State> {
115-
static propTypes = {
116-
getItemString: PropTypes.func.isRequired,
117-
nodeTypeIndicator: PropTypes.any,
118-
nodeType: PropTypes.string.isRequired,
119-
data: PropTypes.any,
120-
hideRoot: PropTypes.bool.isRequired,
121-
createItemString: PropTypes.func.isRequired,
122-
styling: PropTypes.func.isRequired,
123-
collectionLimit: PropTypes.number,
124-
keyPath: PropTypes.arrayOf(
125-
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
126-
).isRequired,
127-
labelRenderer: PropTypes.func.isRequired,
128-
shouldExpandNode: PropTypes.func,
129-
level: PropTypes.number.isRequired,
130-
sortObjectKeys: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
131-
isCircular: PropTypes.bool,
132-
expandable: PropTypes.bool,
133-
};
134-
135-
static defaultProps = {
136-
data: [],
137-
circularCache: [],
138-
level: 0,
139-
expandable: true,
140-
};
141-
142-
constructor(props: Props) {
143-
super(props);
144-
this.state = getStateFromProps(props);
145-
}
146-
147-
UNSAFE_componentWillReceiveProps(nextProps: Props) {
148-
const nextState = getStateFromProps(nextProps);
149-
if (getStateFromProps(this.props).expanded !== nextState.expanded) {
150-
this.setState(nextState);
123+
// When certain props change, we need to re-compute whether our node should be in an expanded state
124+
useEffect(() => {
125+
setExpanded(() => {
126+
return !isCircular ? shouldExpandNode(keyPath, data, level) : false;
127+
});
128+
}, [isCircular, data, keyPath, level, shouldExpandNode]);
129+
130+
// fixme - previously this was happening after a component should update
131+
// this should be moved to a useMemo and updated only when some props change
132+
const renderedChildren =
133+
expanded || (hideRoot && props.level === 0)
134+
? renderChildNodes({ ...props, level: props.level + 1 })
135+
: null;
136+
137+
const itemType = (
138+
<span {...styling('nestedNodeItemType', expanded)}>
139+
{nodeTypeIndicator}
140+
</span>
141+
);
142+
const renderedItemString = getItemString(
143+
nodeType,
144+
data,
145+
itemType,
146+
createItemString(data, collectionLimit),
147+
keyPath
148+
);
149+
const stylingArgs = [keyPath, nodeType, expanded, expandable] as const;
150+
151+
const expandableLatest = useRef<boolean>(expandable);
152+
expandableLatest.current = expandable;
153+
const handleClick = useCallback(() => {
154+
if (expandableLatest.current) {
155+
setExpanded((prevValue) => !prevValue);
151156
}
152-
}
153-
154-
shouldComponentUpdate(nextProps: Props, nextState: State) {
155-
return (
156-
!!Object.keys(nextProps).find(
157-
(key) =>
158-
key !== 'circularCache' &&
159-
(key === 'keyPath'
160-
? nextProps[key].join('/') !== this.props[key].join('/')
161-
: nextProps[key as keyof Props] !== this.props[key as keyof Props])
162-
) || nextState.expanded !== this.state.expanded
163-
);
164-
}
165-
166-
render() {
167-
const {
168-
getItemString,
169-
nodeTypeIndicator,
170-
nodeType,
171-
data,
172-
hideRoot,
173-
createItemString,
174-
styling,
175-
collectionLimit,
176-
keyPath,
177-
labelRenderer,
178-
expandable,
179-
} = this.props;
180-
const { expanded } = this.state;
181-
const renderedChildren =
182-
expanded || (hideRoot && this.props.level === 0)
183-
? renderChildNodes({ ...this.props, level: this.props.level + 1 })
184-
: null;
185-
186-
const itemType = (
187-
<span {...styling('nestedNodeItemType', expanded)}>
188-
{nodeTypeIndicator}
157+
}, []);
158+
159+
return hideRoot ? (
160+
<li {...styling('rootNode', ...stylingArgs)}>
161+
<ul {...styling('rootNodeChildren', ...stylingArgs)}>
162+
{renderedChildren}
163+
</ul>
164+
</li>
165+
) : (
166+
<li {...styling('nestedNode', ...stylingArgs)}>
167+
{expandable && (
168+
<JSONArrow
169+
styling={styling}
170+
nodeType={nodeType}
171+
expanded={expanded}
172+
onClick={handleClick}
173+
/>
174+
)}
175+
<label
176+
{...styling(['label', 'nestedNodeLabel'], ...stylingArgs)}
177+
onClick={handleClick}
178+
>
179+
{labelRenderer(...stylingArgs)}
180+
</label>
181+
<span
182+
{...styling('nestedNodeItemString', ...stylingArgs)}
183+
onClick={handleClick}
184+
>
185+
{renderedItemString}
189186
</span>
190-
);
191-
const renderedItemString = getItemString(
192-
nodeType,
193-
data,
194-
itemType,
195-
createItemString(data, collectionLimit),
196-
keyPath
197-
);
198-
const stylingArgs = [keyPath, nodeType, expanded, expandable] as const;
199-
200-
return hideRoot ? (
201-
<li {...styling('rootNode', ...stylingArgs)}>
202-
<ul {...styling('rootNodeChildren', ...stylingArgs)}>
203-
{renderedChildren}
204-
</ul>
205-
</li>
206-
) : (
207-
<li {...styling('nestedNode', ...stylingArgs)}>
208-
{expandable && (
209-
<JSONArrow
210-
styling={styling}
211-
nodeType={nodeType}
212-
expanded={expanded}
213-
onClick={this.handleClick}
214-
/>
215-
)}
216-
<label
217-
{...styling(['label', 'nestedNodeLabel'], ...stylingArgs)}
218-
onClick={this.handleClick}
219-
>
220-
{labelRenderer(...stylingArgs)}
221-
</label>
222-
<span
223-
{...styling('nestedNodeItemString', ...stylingArgs)}
224-
onClick={this.handleClick}
225-
>
226-
{renderedItemString}
227-
</span>
228-
<ul {...styling('nestedNodeChildren', ...stylingArgs)}>
229-
{renderedChildren}
230-
</ul>
231-
</li>
232-
);
233-
}
234-
235-
handleClick = () => {
236-
if (this.props.expandable) {
237-
this.setState({ expanded: !this.state.expanded });
238-
}
239-
};
187+
<ul {...styling('nestedNodeChildren', ...stylingArgs)}>
188+
{renderedChildren}
189+
</ul>
190+
</li>
191+
);
240192
}
193+
194+
JSONNestedNode.propTypes = {
195+
getItemString: PropTypes.func.isRequired,
196+
nodeTypeIndicator: PropTypes.any,
197+
nodeType: PropTypes.string.isRequired,
198+
data: PropTypes.any,
199+
hideRoot: PropTypes.bool.isRequired,
200+
createItemString: PropTypes.func.isRequired,
201+
styling: PropTypes.func.isRequired,
202+
collectionLimit: PropTypes.number,
203+
keyPath: PropTypes.arrayOf(
204+
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
205+
).isRequired,
206+
labelRenderer: PropTypes.func.isRequired,
207+
shouldExpandNode: PropTypes.func,
208+
level: PropTypes.number.isRequired,
209+
sortObjectKeys: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
210+
isCircular: PropTypes.bool,
211+
expandable: PropTypes.bool,
212+
};

0 commit comments

Comments
 (0)