You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现含子项的递归数组拖拽重排?报错问题求助

Fixing Drag-and-Drop Reordering for Recursive Tree Components

Hey there! Let's break down your issues step by step and get that drag-and-drop working smoothly.


1. Fixing Stack Overflow & Duplicate Items in Add Logic

The root problem here is likely two-fold: state being duplicated across recursive components and mutating state directly instead of using immutable updates. Let's fix both:

a. Lift State to a Parent Component

Right now, you're initializing useReducer inside the TreeItems component, which means every recursive instance of TreeItems gets its own copy of the state. That's a recipe for chaos when trying to update the tree globally. Instead, move the state management to a top-level parent component:

// Parent component (holds the single source of truth)
import { useReducer } from 'react';
import TreeItems from './TreeItems';
import Draggable from "../Hooks/Draggable";

const initialData = [
  { id: 0, text: "test one" },
  { id: 1, text: "test tow" },
  { id: 2, text: "test three" },
  { id: 3, text: "main", sub: [
      { id: 4, text: "sub 1" },
      { id: 5, text: "sub 2" },
    ],
  },
];

function TreeContainer() {
  const [state, dispatch] = useReducer(reducer, initialData);

  // Pass this handler to TreeItems to trigger reorder actions
  const handleReorder = (dragItem, targetParentId, insertIndex) => {
    dispatch({
      type: 'REORDER_TREE',
      payload: { dragItem, targetParentId, insertIndex }
    });
  };

  return <TreeItems data={state} onReorder={handleReorder} />;
}

// Updated TreeItems (only handles rendering, not state)
function TreeItems({ data, onReorder }) {
  return data.map((item) => (
    <TreeItem 
      key={item.id} // Use item.id instead of index! Indexes change when sorting
      nodeId={`${item.id}`} 
      label={
        <div draggable={true} {...Draggable(item, onReorder)}>
          {item.text}
        </div>
      }
    >
      {item.sub && <TreeItems data={item.sub} onReorder={onReorder} />}
    </TreeItem>
  ));
}

b. Rewrite Reducer with Immutable Recursive Updates

Your add logic was probably causing stack overflow because you were mutating the original state or creating infinite recursion while traversing the tree. Use these helper functions to safely remove and insert items without mutation:

// Helper: Recursively remove the dragged item from the tree
const removeDragItem = (items, dragItemId) => {
  return items
    .filter(item => item.id !== dragItemId)
    .map(item => ({
      ...item,
      ...(item.sub && { sub: removeDragItem(item.sub, dragItemId) })
    }));
};

// Helper: Recursively find the target parent and insert the item
const insertDragItem = (items, targetParentId, insertIndex, dragItem) => {
  // Insert at root if targetParentId is null
  if (targetParentId === null) {
    const newRoot = [...items];
    newRoot.splice(insertIndex, 0, dragItem);
    return newRoot;
  }

  return items.map(item => {
    if (item.id === targetParentId) {
      // Insert into the parent's sub array
      const updatedSub = [...(item.sub || [])];
      updatedSub.splice(insertIndex, 0, dragItem);
      return { ...item, sub: updatedSub };
    }
    // Recurse into children if needed
    return item.sub ? { ...item, sub: insertDragItem(item.sub, targetParentId, insertIndex, dragItem) } : item;
  });
};

// Updated reducer
function reducer(state, action) {
  switch (action.type) {
    case 'REORDER_TREE': {
      const { dragItem, targetParentId, insertIndex } = action.payload;
      // Step 1: Remove the dragged item from its original position
      const stateWithoutDrag = removeDragItem(state, dragItem.id);
      // Step 2: Insert it at the target position
      const newState = insertDragItem(stateWithoutDrag, targetParentId, insertIndex, dragItem);
      return newState;
    }
    default:
      return state;
  }
}

c. Update Your Draggable Hook

Make sure your Draggable hook captures the necessary info when dragging ends: the dragged item, the target parent's ID, and the index to insert at. This ensures your handleReorder function has all the data it needs to update the state correctly.


2. Fixing CodeSandbox Material-UI Import Issues

If CodeSandbox isn't resolving Material-UI imports, try these steps:

  • Use the correct package name: Newer Material-UI versions are under the @mui scope. Make sure you're importing from @mui/material, @mui/lab (for TreeItem), etc., not the old material-ui package.
  • Install dependencies explicitly: In CodeSandbox, go to the "Dependencies" tab and add @mui/material, @mui/lab, and @emotion/react/@emotion/styled (required for MUI v5).
  • Verify import paths: Double-check that your imports look like this:
    import TreeItem from '@mui/lab/TreeItem';
    import TreeView from '@mui/lab/TreeView';
    

Quick Additional Tips

  • Never use index as a key: When items are reordered, their indexes change, which causes React to reuse components incorrectly. Always use a unique, stable identifier like item.id.
  • Avoid state mutation: React relies on immutable state updates to detect changes. Always create new arrays/objects instead of modifying existing ones (that's what the spread operator ... is doing in the helpers above).
  • Debug with console logs: If you're still hitting issues, log the state before/after updates in your reducer to see where the duplicate items or infinite recursion is happening.

内容的提问来源于stack exchange,提问作者Ali Husham

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 15:58:00