Skip to content

Commit c844c04

Browse files
authored
chore: Migrating to async safe lifecycle functions (#320)
* added deps * updating eslint * migrating to gDSFP * make sure treeData is from instanceProps * fix test
1 parent 2706488 commit c844c04

File tree

4 files changed

+117
-74
lines changed

4 files changed

+117
-74
lines changed

.eslintrc.json renamed to .eslintrc

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
},
77
"rules": {
88
"react/jsx-filename-extension": 0,
9-
"react/prefer-stateless-function": 0
9+
"react/prefer-stateless-function": 0,
10+
"react/no-did-mount-set-state": 0,
11+
"react/sort-comp": 0
1012
}
1113
}

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"react-dnd": "2.6.0",
6464
"react-dnd-html5-backend": "2.6.0",
6565
"react-dnd-scrollzone": "^4.0.0",
66+
"react-lifecycles-compat": "^3.0.4",
6667
"react-virtualized": "^9.19.1"
6768
},
6869
"peerDependencies": {
@@ -104,11 +105,11 @@
104105
"json-loader": "^0.5.7",
105106
"postcss-loader": "^2.1.5",
106107
"prettier": "^1.12.1",
107-
"react": "^16.3.2",
108+
"react": "^16.2.0",
108109
"react-addons-shallow-compare": "^15.6.2",
109110
"react-dnd-test-backend": "2.6.0",
110111
"react-dnd-touch-backend": "0.4.0",
111-
"react-dom": "^16.3.2",
112+
"react-dom": "^16.2.0",
112113
"react-hot-loader": "^4.2.0",
113114
"react-sortable-tree-theme-file-explorer": "^1.1.2",
114115
"react-test-renderer": "^16.3.2",

src/react-sortable-tree.js

+105-65
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import withScrolling, {
66
createVerticalStrength,
77
createHorizontalStrength,
88
} from 'react-dnd-scrollzone';
9+
import { polyfill } from 'react-lifecycles-compat';
910
import 'react-virtualized/styles.css';
1011
import TreeNode from './tree-node';
1112
import NodeRendererDefault from './node-renderer-default';
@@ -108,6 +109,14 @@ class ReactSortableTree extends Component {
108109
searchMatches: [],
109110
searchFocusTreeIndex: null,
110111
dragging: false,
112+
113+
// props that need to be used in gDSFP or static functions will be stored here
114+
instanceProps: {
115+
treeData: [],
116+
ignoreOneTreeUpdate: false,
117+
searchQuery: null,
118+
searchFocusOffset: null,
119+
},
111120
};
112121

113122
this.toggleChildrenVisibility = this.toggleChildrenVisibility.bind(this);
@@ -120,8 +129,15 @@ class ReactSortableTree extends Component {
120129
}
121130

122131
componentDidMount() {
123-
this.loadLazyChildren();
124-
this.search(this.props);
132+
ReactSortableTree.loadLazyChildren(this.props, this.state);
133+
const stateUpdate = ReactSortableTree.search(
134+
this.props,
135+
this.state,
136+
true,
137+
true,
138+
false
139+
);
140+
this.setState(stateUpdate);
125141

126142
// Hook into react-dnd state changes to detect when the drag ends
127143
// TODO: This is very brittle, so it needs to be replaced if react-dnd
@@ -131,34 +147,49 @@ class ReactSortableTree extends Component {
131147
.subscribeToStateChange(this.handleDndMonitorChange);
132148
}
133149

134-
componentWillReceiveProps(nextProps) {
135-
if (this.props.treeData !== nextProps.treeData) {
136-
// Ignore updates caused by search, in order to avoid infinite looping
137-
if (this.ignoreOneTreeUpdate) {
138-
this.ignoreOneTreeUpdate = false;
139-
} else {
140-
// Reset the focused index if the tree has changed
141-
this.setState({ searchFocusTreeIndex: null });
150+
static getDerivedStateFromProps(nextProps, prevState) {
151+
const { instanceProps } = prevState;
152+
const newState = {};
142153

143-
// Load any children defined by a function
144-
this.loadLazyChildren(nextProps);
154+
// make sure we have the most recent version of treeData
155+
instanceProps.treeData = nextProps.treeData;
145156

146-
this.search(nextProps, false, false);
157+
if (!isEqual(instanceProps.treeData, nextProps.treeData)) {
158+
if (instanceProps.ignoreOneTreeUpdate) {
159+
instanceProps.ignoreOneTreeUpdate = false;
160+
} else {
161+
newState.searchFocusTreeIndex = null;
162+
ReactSortableTree.loadLazyChildren(nextProps, prevState);
163+
Object.assign(
164+
newState,
165+
ReactSortableTree.search(nextProps, prevState, false, false, false)
166+
);
147167
}
148168

149-
// Reset the drag state
150-
this.setState({
151-
draggingTreeData: null,
152-
draggedNode: null,
153-
draggedMinimumTreeIndex: null,
154-
draggedDepth: null,
155-
dragging: false,
156-
});
157-
} else if (!isEqual(this.props.searchQuery, nextProps.searchQuery)) {
158-
this.search(nextProps);
159-
} else if (this.props.searchFocusOffset !== nextProps.searchFocusOffset) {
160-
this.search(nextProps, true, true, true);
169+
newState.draggingTreeData = null;
170+
newState.draggedNode = null;
171+
newState.draggedMinimumTreeIndex = null;
172+
newState.draggedDepth = null;
173+
newState.dragging = false;
174+
} else if (!isEqual(instanceProps.searchQuery, nextProps.searchQuery)) {
175+
Object.assign(
176+
newState,
177+
ReactSortableTree.search(nextProps, prevState, true, true, false)
178+
);
179+
} else if (
180+
instanceProps.searchFocusOffset !== nextProps.searchFocusOffset
181+
) {
182+
Object.assign(
183+
newState,
184+
ReactSortableTree.search(nextProps, prevState, true, true, true)
185+
);
161186
}
187+
188+
instanceProps.searchQuery = nextProps.searchQuery;
189+
instanceProps.searchFocusOffset = nextProps.searchFocusOffset;
190+
newState.instanceProps = instanceProps;
191+
192+
return newState;
162193
}
163194

164195
// listen to dragging
@@ -197,8 +228,10 @@ class ReactSortableTree extends Component {
197228
}
198229

199230
toggleChildrenVisibility({ node: targetNode, path }) {
231+
const { instanceProps } = this.state;
232+
200233
const treeData = changeNodeAtPath({
201-
treeData: this.props.treeData,
234+
treeData: instanceProps.treeData,
202235
path,
203236
newNode: ({ node }) => ({ ...node, expanded: !node.expanded }),
204237
getNodeKey: this.props.getNodeKey,
@@ -250,46 +283,40 @@ class ReactSortableTree extends Component {
250283
});
251284
}
252285

253-
search(
254-
props = this.props,
255-
seekIndex = true,
256-
expand = true,
257-
singleSearch = false
258-
) {
286+
// returns the new state after search
287+
static search(props, state, seekIndex, expand, singleSearch) {
259288
const {
260-
treeData,
261289
onChange,
290+
getNodeKey,
262291
searchFinishCallback,
263292
searchQuery,
264293
searchMethod,
265294
searchFocusOffset,
266295
onlyExpandSearchedNodes,
267296
} = props;
268297

269-
// Skip search if no conditions are specified
270-
if (
271-
(searchQuery === null ||
272-
typeof searchQuery === 'undefined' ||
273-
String(searchQuery) === '') &&
274-
!searchMethod
275-
) {
276-
this.setState({
277-
searchMatches: [],
278-
});
298+
const { instanceProps } = state;
279299

300+
// Skip search if no conditions are specified
301+
if (!searchQuery && !searchMethod) {
280302
if (searchFinishCallback) {
281303
searchFinishCallback([]);
282304
}
283305

284-
return;
306+
return { searchMatches: [] };
285307
}
286308

309+
const newState = {};
310+
287311
// if onlyExpandSearchedNodes collapse the tree and search
288312
const { treeData: expandedTreeData, matches: searchMatches } = find({
289-
getNodeKey: this.props.getNodeKey,
313+
getNodeKey,
290314
treeData: onlyExpandSearchedNodes
291-
? toggleExpandedForAll({ treeData, expanded: false })
292-
: treeData,
315+
? toggleExpandedForAll({
316+
treeData: instanceProps.treeData,
317+
expanded: false,
318+
})
319+
: instanceProps.treeData,
293320
searchQuery,
294321
searchMethod: searchMethod || defaultSearchMethod,
295322
searchFocusOffset,
@@ -299,7 +326,7 @@ class ReactSortableTree extends Component {
299326

300327
// Update the tree with data leaving all paths leading to matching nodes open
301328
if (expand) {
302-
this.ignoreOneTreeUpdate = true; // Prevents infinite loop
329+
newState.ignoreOneTreeUpdate = true; // Prevents infinite loop
303330
onChange(expandedTreeData);
304331
}
305332

@@ -316,20 +343,20 @@ class ReactSortableTree extends Component {
316343
searchFocusTreeIndex = searchMatches[searchFocusOffset].treeIndex;
317344
}
318345

319-
this.setState({
320-
searchMatches,
321-
searchFocusTreeIndex,
322-
});
346+
newState.searchMatches = searchMatches;
347+
newState.searchFocusTreeIndex = searchFocusTreeIndex;
348+
349+
return newState;
323350
}
324351

325352
startDrag({ path }) {
326-
this.setState(() => {
353+
this.setState((prevState) => {
327354
const {
328355
treeData: draggingTreeData,
329356
node: draggedNode,
330357
treeIndex: draggedMinimumTreeIndex,
331358
} = removeNode({
332-
treeData: this.props.treeData,
359+
treeData: prevState.instanceProps.treeData,
333360
path,
334361
getNodeKey: this.props.getNodeKey,
335362
});
@@ -349,6 +376,8 @@ class ReactSortableTree extends Component {
349376
depth: draggedDepth,
350377
minimumTreeIndex: draggedMinimumTreeIndex,
351378
}) {
379+
const { instanceProps } = this.state;
380+
352381
// Ignore this hover if it is at the same position as the last hover
353382
if (
354383
this.state.draggedDepth === draggedDepth &&
@@ -359,7 +388,8 @@ class ReactSortableTree extends Component {
359388

360389
// Fall back to the tree data if something is being dragged in from
361390
// an external element
362-
const draggingTreeData = this.state.draggingTreeData || this.props.treeData;
391+
const draggingTreeData =
392+
this.state.draggingTreeData || instanceProps.treeData;
363393

364394
const addedResult = memoizedInsertNode({
365395
treeData: draggingTreeData,
@@ -391,6 +421,8 @@ class ReactSortableTree extends Component {
391421
}
392422

393423
endDrag(dropResult) {
424+
const { instanceProps } = this.state;
425+
394426
const resetTree = () =>
395427
this.setState({
396428
draggingTreeData: null,
@@ -415,13 +447,13 @@ class ReactSortableTree extends Component {
415447
});
416448
}
417449

418-
let treeData = this.state.draggingTreeData || this.props.treeData;
450+
let treeData = this.state.draggingTreeData || instanceProps.treeData;
419451

420452
// If copying is enabled, a drop outside leaves behind a copy in the
421453
// source tree
422454
if (shouldCopy) {
423455
treeData = changeNodeAtPath({
424-
treeData: this.props.treeData, // use treeData unaltered by the drag operation
456+
treeData: instanceProps.treeData, // use treeData unaltered by the drag operation
425457
path,
426458
newNode: ({ node: copyNode }) => ({ ...copyNode }), // create a shallow copy of the node
427459
getNodeKey: this.props.getNodeKey,
@@ -448,10 +480,13 @@ class ReactSortableTree extends Component {
448480
}
449481

450482
// Load any children in the tree that are given by a function
451-
loadLazyChildren(props = this.props) {
483+
// calls the onChange callback on the new treeData
484+
static loadLazyChildren(props, state) {
485+
const { instanceProps } = state;
486+
452487
walk({
453-
treeData: props.treeData,
454-
getNodeKey: this.props.getNodeKey,
488+
treeData: instanceProps.treeData,
489+
getNodeKey: props.getNodeKey,
455490
callback: ({ node, path, lowerSiblingCounts, treeIndex }) => {
456491
// If the node has children defined by a function, and is either expanded
457492
// or set to load even before expansion, run the function.
@@ -469,9 +504,9 @@ class ReactSortableTree extends Component {
469504

470505
// Provide a helper to append the new data when it is received
471506
done: childrenArray =>
472-
this.props.onChange(
507+
props.onChange(
473508
changeNodeAtPath({
474-
treeData: this.props.treeData,
509+
treeData: instanceProps.treeData,
475510
path,
476511
newNode: ({ node: oldNode }) =>
477512
// Only replace the old node if it's the one we set off to find children
@@ -482,7 +517,7 @@ class ReactSortableTree extends Component {
482517
children: childrenArray,
483518
}
484519
: oldNode,
485-
getNodeKey: this.props.getNodeKey,
520+
getNodeKey: props.getNodeKey,
486521
})
487522
),
488523
});
@@ -492,9 +527,11 @@ class ReactSortableTree extends Component {
492527
}
493528

494529
renderRow(
495-
{ node, parentNode, path, lowerSiblingCounts, treeIndex },
530+
row,
496531
{ listIndex, style, getPrevRow, matchKeys, swapFrom, swapDepth, swapLength }
497532
) {
533+
const { node, parentNode, path, lowerSiblingCounts, treeIndex } = row;
534+
498535
const {
499536
canDrag,
500537
generateNodeProps,
@@ -572,9 +609,10 @@ class ReactSortableTree extends Component {
572609
draggedNode,
573610
draggedDepth,
574611
draggedMinimumTreeIndex,
612+
instanceProps,
575613
} = this.state;
576614

577-
const treeData = this.state.draggingTreeData || this.props.treeData;
615+
const treeData = this.state.draggingTreeData || instanceProps.treeData;
578616

579617
let rows;
580618
let swapFrom = null;
@@ -870,6 +908,8 @@ ReactSortableTree.contextTypes = {
870908
dragDropManager: PropTypes.shape({}),
871909
};
872910

911+
polyfill(ReactSortableTree);
912+
873913
// Export the tree component without the react-dnd DragDropContext,
874914
// for when component is used with other components using react-dnd.
875915
// see: https://github.com/gaearon/react-dnd/issues/186

yarn.lock

+6-6
Original file line numberDiff line numberDiff line change
@@ -8593,9 +8593,9 @@ react-docgen@^3.0.0-beta11:
85938593
node-dir "^0.1.10"
85948594
recast "^0.12.6"
85958595

8596-
react-dom@^16.3.2:
8597-
version "16.3.2"
8598-
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df"
8596+
react-dom@^16.2.0:
8597+
version "16.4.0"
8598+
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.0.tgz#099f067dd5827ce36a29eaf9a6cdc7cbf6216b1e"
85998599
dependencies:
86008600
fbjs "^0.8.16"
86018601
loose-envify "^1.1.0"
@@ -8747,9 +8747,9 @@ react-virtualized@^9.19.1:
87478747
prop-types "^15.6.0"
87488748
react-lifecycles-compat "^3.0.4"
87498749

8750-
react@^16.3.2:
8751-
version "16.3.2"
8752-
resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9"
8750+
react@^16.2.0:
8751+
version "16.4.0"
8752+
resolved "https://registry.yarnpkg.com/react/-/react-16.4.0.tgz#402c2db83335336fba1962c08b98c6272617d585"
87538753
dependencies:
87548754
fbjs "^0.8.16"
87558755
loose-envify "^1.1.0"

0 commit comments

Comments
 (0)