lukasbach / react-complex-tree

Unopinionated Accessible Tree Component with Multi-Select and Drag-And-Drop
https://rct.lukasbach.com
MIT License
944 stars 74 forks source link

Drag and drop crashes. #256

Closed BaikalCode closed 1 year ago

BaikalCode commented 1 year ago

Description:

Drag and drop crashes. If you try to move an item anywhere, there is a periodic error (details below) that leaves a white screen behind.

Stack:

Webpack + React + TypeScript + Redux Toolkit

Component implementation:

const NavigationTree = () => {
    const {
        treeId,
        tree,
        environment,
        items,
        isEmpty,
        changeHandlers: {
            focusedItem,
            expandedItems,
            selectedItems,
            handleDrop,
            handleFocusItem,
            handleExpandItem,
            handleSelectItems,
            handleCollapseItem,
            handleMissingItems,
        },
    } = useNavigationTree();

return !isEmpty && (
        <ControlledTreeEnvironment
            ref={ environment }
            items={ items }
            viewState={ {
                [treeId]: {
                    focusedItem,
                    expandedItems,
                    selectedItems,
                },
            } }
            getItemTitle={ item => item.data }
            renderItem={ renderNavigationTreeItem }
            renderItemArrow={ renderNavigationTreeItemArrow }
            renderDragBetweenLine={ () => <div className="ui-test-test-test" /> }
            canDragAndDrop
            canReorderItems
            canDropOnFolder
            canDropOnNonFolder
            showLiveDescription={ false }
            // onDrop={ handleDrop }
            onFocusItem={ handleFocusItem }
            onExpandItem={ handleExpandItem }
            onSelectItems={ handleSelectItems }
            onCollapseItem={ handleCollapseItem }
            onMissingItems={ handleMissingItems }
        >
            <Tree
                ref={ tree }
                treeId={ treeId }
                rootItem="root"
                treeLabel="Navigation Tree"
            />
        </ControlledTreeEnvironment>
    );
};

Hooks implementation:

export const useNavigationTree = () => {
    const tree = useRef(null);
    const environment = useRef(null);
    const treeId = `navigation-tree-${ uuid() }`;
    const items = useSelector(state => state.navigationTree.items);
    const isEmpty = useMemo(() => Object.keys(items).length === 0, [ items ]);

    return {
        tree,
        environment,
        treeId,
        items,
        isEmpty,
        changeHandlers: useTreeChangeHandlers(),
    };
};
export const useTreeChangeHandlers = () => {
    const dispatch = useDispatch();
    const focusedItem = useSelector(state => state.navigationTree.focusedItem);
    const expandedItems = useSelector(state => state.navigationTree.expandedItems);
    const selectedItems = useSelector(state => state.navigationTree.selectedItems);

    const handleFocusItem = item => dispatch(navigationTreeSlice.actions.setFocusedItem(item.index));
    const handleSelectItems = items => dispatch(navigationTreeSlice.actions.setSelectedItems(items));
    const handleExpandItem = item => dispatch(navigationTreeSlice.actions.setExpandedItems(item.index));
    const handleCollapseItem = item => dispatch(navigationTreeSlice.actions.setExpandedItems(expandedItems.filter(
        expandedItemIndex => expandedItemIndex !== item.index)));
    const handleMissingItems = items => alert(`We should now load the items ${ items.join(', ') }...`);
    const handleDrop = (items, target) => dispatch(navigationTreeSlice.actions.changeItemChildren({
        itemIndex: target?.targetItem,
        parentIndex: target?.parentItem,
        children: items,
    }));

    return {
        focusedItem,
        expandedItems,
        selectedItems,
        handleDrop,
        handleFocusItem,
        handleExpandItem,
        handleSelectItems,
        handleCollapseItem,
        handleMissingItems,
    };
};

Errors Screenshots

Снимок Снимок Снимок

lukasbach commented 1 year ago

This line

    const treeId = `navigation-tree-${ uuid() }`;

looks like your treeId is regenerated during each re-render. I would imagine that this cases RTC to loose the reference on the tree on the next rerender, and can't access it's internal state after that. Can you look into if that might be the cause?

BaikalCode commented 1 year ago

You're absolutely right, thank you, my mistake, I didn't see it coming.