diff --git a/examples/basic-example/app.js b/examples/basic-example/app.js index 8082402f..a1a36ed4 100644 --- a/examples/basic-example/app.js +++ b/examples/basic-example/app.js @@ -142,6 +142,11 @@ class App extends Component { }, ], }, + { + title: 'Children will be added dynamically', + subtitle: 'If you specify children with a function', + children: params => this.generateNewChildren(params), + }, ], }; @@ -171,6 +176,19 @@ class App extends Component { this.expand(false); } + generateNewChildren(params) { + setTimeout(() => { + const childNodes = Array(5) + .fill() + .map(() => ({ + title: 'Dynamic child node', + subtitle: `parent path: ${params.path.join('->')}`, + children: x => this.generateNewChildren(x), + })); + params.done(childNodes); + }, 1000); + } + render() { const projectName = 'React Sortable Tree'; const authorName = 'Chris Fritz'; diff --git a/src/react-sortable-tree.js b/src/react-sortable-tree.js index 76df00de..388ef963 100644 --- a/src/react-sortable-tree.js +++ b/src/react-sortable-tree.js @@ -202,7 +202,7 @@ class ReactSortableTree extends Component { depth, minimumTreeIndex, }) { - const { treeData, treeIndex, path } = insertNode({ + const insertedNode = insertNode({ treeData: this.state.draggingTreeData, newNode: node, depth, @@ -210,7 +210,11 @@ class ReactSortableTree extends Component { expandParent: true, getNodeKey: this.props.getNodeKey, }); + if (!insertedNode) { + return; + } + const { treeData, treeIndex, path } = insertedNode; this.props.onChange(treeData); this.props.onMoveNode({ @@ -339,6 +343,9 @@ class ReactSortableTree extends Component { expandParent: true, getNodeKey: this.props.getNodeKey, }); + if (!addedResult) { + return; + } const rows = this.getRows(addedResult.treeData); const expandedParentPath = rows[addedResult.treeIndex].path; @@ -553,16 +560,19 @@ class ReactSortableTree extends Component { expandParent: true, getNodeKey, }); - - const swapTo = draggedMinimumTreeIndex; - swapFrom = addedResult.treeIndex; - swapLength = 1 + memoizedGetDescendantCount({ node: draggedNode }); - rows = slideRows( - this.getRows(addedResult.treeData), - swapFrom, - swapTo, - swapLength - ); + if (addedResult) { + const swapTo = draggedMinimumTreeIndex; + swapFrom = addedResult.treeIndex; + swapLength = 1 + memoizedGetDescendantCount({ node: draggedNode }); + rows = slideRows( + this.getRows(addedResult.treeData), + swapFrom, + swapTo, + swapLength + ); + } else { + rows = this.getRows(treeData); + } } else { rows = this.getRows(treeData); } diff --git a/src/utils/dnd-manager.js b/src/utils/dnd-manager.js index 38349a22..1e136afa 100644 --- a/src/utils/dnd-manager.js +++ b/src/utils/dnd-manager.js @@ -142,6 +142,9 @@ export default class DndManager { minimumTreeIndex: dropTargetProps.listIndex, expandParent: true, }); + if (!addedResult) { + return false; + } return this.customCanDrop({ node, diff --git a/src/utils/tree-data-utils.js b/src/utils/tree-data-utils.js index ca24ec4f..5274f7e1 100644 --- a/src/utils/tree-data-utils.js +++ b/src/utils/tree-data-utils.js @@ -36,21 +36,24 @@ function getNodeDataAtTreeIndexOrNextIndex({ let childIndex = currentIndex + 1; const childCount = node.children.length; for (let i = 0; i < childCount; i += 1) { - const result = getNodeDataAtTreeIndexOrNextIndex({ - ignoreCollapsed, - getNodeKey, - targetIndex, - node: node.children[i], - currentIndex: childIndex, - lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], - path: selfPath, - }); + const childNode = node.children[i]; + if (childNode) { + const result = getNodeDataAtTreeIndexOrNextIndex({ + ignoreCollapsed, + getNodeKey, + targetIndex, + node: node.children[i], + currentIndex: childIndex, + lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], + path: selfPath, + }); - if (result.node) { - return result; - } + if (result.node) { + return result; + } - childIndex = result.nextIndex; + childIndex = result.nextIndex; + } } // If the target node is not found, return the farthest traversed index @@ -135,20 +138,23 @@ function walkDescendants({ const childCount = node.children.length; if (typeof node.children !== 'function') { for (let i = 0; i < childCount; i += 1) { - childIndex = walkDescendants({ - callback, - getNodeKey, - ignoreCollapsed, - node: node.children[i], - parentNode: isPseudoRoot ? null : node, - currentIndex: childIndex + 1, - lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], - path: selfPath, - }); + const childNode = node.children[i]; + if (childNode) { + childIndex = walkDescendants({ + callback, + getNodeKey, + ignoreCollapsed, + node: childNode, + parentNode: isPseudoRoot ? null : node, + currentIndex: childIndex + 1, + lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], + path: selfPath, + }); - // Cut walk short if the callback returned false - if (childIndex === false) { - return false; + // Cut walk short if the callback returned false + if (childIndex === false) { + return false; + } } } } @@ -710,24 +716,24 @@ function addNodeAtDepthAndIndex({ (isLastChild && !(node.children && node.children.length)) ) { if (typeof node.children === 'function') { - throw new Error('Cannot add to children defined by a function'); - } else { - const extraNodeProps = expandParent ? { expanded: true } : {}; - const nextNode = { - ...node, + // Cannot add to children defined by a function + return undefined; + } + const extraNodeProps = expandParent ? { expanded: true } : {}; + const nextNode = { + ...node, - ...extraNodeProps, - children: node.children ? [newNode, ...node.children] : [newNode], - }; + ...extraNodeProps, + children: node.children ? [newNode, ...node.children] : [newNode], + }; - return { - node: nextNode, - nextIndex: currentIndex + 2, - insertedTreeIndex: currentIndex + 1, - parentPath: selfPath(nextNode), - parentNode: isPseudoRoot ? null : nextNode, - }; - } + return { + node: nextNode, + nextIndex: currentIndex + 2, + insertedTreeIndex: currentIndex + 1, + parentPath: selfPath(nextNode), + parentNode: isPseudoRoot ? null : nextNode, + }; } // If this is the target depth for the insertion, @@ -828,6 +834,9 @@ function addNodeAtDepthAndIndex({ getNodeKey, path: [], // Cannot determine the parent path until the children have been processed }); + if (!mapResult) { + return undefined; + } if ('insertedTreeIndex' in mapResult) { ({ @@ -908,7 +917,8 @@ export function insertNode({ }); if (!('insertedTreeIndex' in insertResult)) { - throw new Error('No suitable position found to insert.'); + // No suitable position found to insert + return undefined; } const treeIndex = insertResult.insertedTreeIndex;