|
1 |
| -import React from 'react'; |
| 1 | +import React, { useCallback, useEffect, useRef, useState } from 'react'; |
2 | 2 | import PropTypes from 'prop-types';
|
3 | 3 | import JSONArrow from './JSONArrow';
|
4 | 4 | import getCollectionEntries from './getCollectionEntries';
|
@@ -97,144 +97,116 @@ interface Props extends CircularPropsPassedThroughJSONNestedNode {
|
97 | 97 | expandable: boolean;
|
98 | 98 | }
|
99 | 99 |
|
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; |
103 | 117 |
|
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 | + }); |
113 | 122 |
|
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); |
151 | 156 | }
|
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} |
189 | 186 | </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 | + ); |
240 | 192 | }
|
| 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