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

使用DndProvider后导入useDrop仍触发drag drop context错误

问题:使用useDrop Hook后抛出'Uncaught Invariant Violation: Expected drag drop context'错误

主文件已用DndProvider包裹组件,此前运行正常,但导入并使用useDrop Hook后出现上述错误。相关代码如下:

/* eslint-disable no-unused-vars */
import React, { useState } from 'react'
import Sidebar from './AAComponents/AASidebar'
import DraggableItem from './AAComponents/AADraggableItem'
import DndProviderWrapper from './AADndProviderWrapper'
import { DndProvider, useDrop } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'

const AAWhatsapp = () => {
  const [draggedComponents, setDraggedComponents] = useState([])
  const [canvasSize, setCanvasSize] = useState({ width: 800, height: 600 })

  const handleSidebarItemClick = (componentType) => {
    setDraggedComponents((prev) => [...prev, { type: componentType, position: { x: 0, y: 0 } }])
  }

  const handleDrag = (index, delta) => {
    setDraggedComponents((prev) => {
      const updatedComponents = [...prev]
      updatedComponents[index].position.x += delta.x
      updatedComponents[index].position.y += delta.y

      const canvasWidth = canvasSize.width
      const canvasHeight = canvasSize.height

      if (updatedComponents[index].position.x < 0) {
        updatedComponents[index].position.x = 0
      } else if (updatedComponents[index].position.x + 100 > canvasWidth) {
        updatedComponents[index].position.x = canvasWidth - 100
      }

      if (updatedComponents[index].position.y < 0) {
        updatedComponents[index].position.y = 0
      } else if (updatedComponents[index].position.y + 50 > canvasHeight) {
        updatedComponents[index].position.y = canvasHeight - 50
      }

      return updatedComponents
    })
  }

  const handleDropFromSidebar = (item) => {
    const newItem = {
      type: item.type,
      position: { x: 100, y: 100 }
    }

    setDraggedComponents((prev) => [...prev, newItem])
  }

  const [, drop] = useDrop({
    accept: 'SIDEBAR_ITEM',
    drop: (item) => handleDropFromSidebar(item)
  })

  return (
    <DndProvider backend={HTML5Backend}>
      <div style={{ display: 'flex' }}>
        <Sidebar onItemClick={handleSidebarItemClick} />
        <div
          ref={drop}
          style={{
            flex: 1,
            padding: '20px',
            position: 'relative',
            width: canvasSize.width,
            height: canvasSize.height,
            border: '1px solid #ccc',
            overflow: 'hidden'
          }}
        >
          {draggedComponents.map((component, index) => (
            <DraggableItem key={index} index={index} component={component} onDrag={handleDrag} />
          ))}
        </div>
      </div>
    </DndProvider>
  )
}

export default AAWhatsapp

其余关联代码:

AASidebar.js

/* eslint-disable no-unused-vars */
import React from 'react'
import SidebarItem from './AASidebarItem'

const AASidebar = ({ onItemClick }) => {
  const handleItemClick = (component) => {
    onItemClick(component)
  }


  return (
    <div className='side' style={{ width: '200px', background: '#f0f0f0', padding: '20px' }}>
    <SidebarItem type='Text' />
    <SidebarItem type='Image' />
  </div>
  )
}

export default AASidebar

AASidebarItem.js

import React from 'react'
import { useDrag } from 'react-dnd'

const AASidebarItem = ({ type }) => {
  const [{ isDragging }, drag] = useDrag({
    type: 'SIDEBAR_ITEM', // Set a unique type for sidebar items
    item: { type },
    collect: (monitor) => ({
      isDragging: !!monitor.isDragging()
    })
  })

  return (
    <div
      ref={drag}
      className='m-1 bg-success cursor-pointer'
      style={{ opacity: isDragging ? 0.5 : 1 }}
    >
      {type}
    </div>
  )
}

export default AASidebarItem

AADraggableItem.js

import React from 'react'
import { useDrag } from 'react-dnd'

const AADraggableItem = ({ component, index, onDrag }) => {
  const [{ isDragging }, drag] = useDrag({
    type: 'COMPONENT',
    item: { type: component.type, index },
    collect: (monitor) => ({
      isDragging: !!monitor.isDragging()
    }),
    end: (item, monitor) => {
      const delta = monitor.getDifferenceFromInitialOffset()
      if (delta) {
        onDrag(index, delta)
      }
    }
  })

  const getDynamicStyles = () => {
    switch (component.type) {
      case 'Text':
        return { width: '100px', height: '50px', fontSize: '16px', fontWeight: 'bold' }
      case 'Image':
        return { width: '150px', height: '100px' }
      default:
        return { width: '100px', height: '50px' }
    }
  }

  const dynamicStyles = getDynamicStyles()

  const renderItemContent = (component) => {
    switch (component.type) {
      case 'Text':
        return <span>A Text Item</span>
      case 'Image':
        return <img src="path/to/image.png" alt="Image Item" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
      default:
        return <span>{component.type}</span>
    }
  }

  return (
    <div
      ref={(node) => drag(node)}
      style={{
        width: dynamicStyles.width,
        height: dynamicStyles.height,
        background: isDragging ? '#87CEEB' : '#e0e0e0',
        position: 'absolute',
        left: component.position.x,
        top: component.position.y,
        cursor: 'move',
        opacity: isDragging ? 0 : 1,
        border: '1px solid #000',
        ...dynamicStyles 
      }}
    >
      {renderItemContent(component)}
    </div>
  )
}

export default AADraggableItem

已尝试用独立的DndProviderWrapper包裹整个应用,但问题未解决。


错误原因

你在AAWhatsapp组件内部直接调用了useDrop Hook,但DndProvider是该组件返回值里才渲染的——组件执行useDrop时,DndProvider还未挂载,当前组件并不在DndContext的范围内,所以react-dnd找不到上下文,抛出该错误。

核心问题:Hook调用时机早于DndProvider的挂载,组件本身不在DndContext包裹下。


修复方案

把useDrop的逻辑抽离到独立子组件中,让子组件被DndProvider包裹,这样调用useDrop时就能正常获取上下文。

步骤1:创建DropCanvas组件

新建专门处理画布drop逻辑的组件:

import React from 'react';
import { useDrop } from 'react-dnd';

const DropCanvas = ({ canvasSize, draggedComponents, DraggableItem, onDrag, onAddComponent }) => {
  const handleDropFromSidebar = (item) => {
    const newItem = {
      type: item.type,
      position: { x: 100, y: 100 }
    };
    onAddComponent(newItem);
  };

  const [, drop] = useDrop({
    accept: 'SIDEBAR_ITEM',
    drop: (item) => handleDropFromSidebar(item)
  });

  return (
    <div
      ref={drop}
      style={{
        flex: 1,
        padding: '20px',
        position: 'relative',
        width: canvasSize.width,
        height: canvasSize.height,
        border: '1px solid #ccc',
        overflow: 'hidden'
      }}
    >
      {draggedComponents.map((component, index) => (
        <DraggableItem key={index} index={index} component={component} onDrag={onDrag} />
      ))}
    </div>
  );
};

export default DropCanvas;

步骤2:修改AAWhatsapp组件

替换原画布部分为DropCanvas,确保DndProvider包裹整个子组件树:

/* eslint-disable no-unused-vars */
import React, { useState } from 'react'
import Sidebar from './AAComponents/AASidebar'
import DraggableItem from './AAComponents/AADraggableItem'
import DropCanvas from './DropCanvas'; // 导入新组件
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'

const AAWhatsapp = () => {
  const [draggedComponents, setDraggedComponents] = useState([])
  const [canvasSize, setCanvasSize] = useState({ width: 800, height: 600 })

  const handleSidebarItemClick = (componentType) => {
    setDraggedComponents((prev) => [...prev, { type: componentType, position: { x: 0, y: 0 } }])
  }

  const handleDrag = (index, delta) => {
    setDraggedComponents((prev) => {
      const updatedComponents = [...prev]
      updatedComponents[index].position.x += delta.x
      updatedComponents[index].position.y += delta.y

      const canvasWidth = canvasSize.width
      const canvasHeight = canvasSize.height

      if (updatedComponents[index].position.x < 0) {
        updatedComponents[index].position.x = 0
      } else if (updatedComponents[index].position.x + 100 > canvasWidth) {
        updatedComponents[index].position.x = canvasWidth - 100
      }

      if (updatedComponents[index].position.y < 0) {
        updatedComponents[index].position.y = 0
      } else if (updatedComponents[index].position.y + 50 > canvasHeight) {
        updatedComponents[index].position.y = canvasHeight - 50
      }

      return updatedComponents
    })
  }

  const handleAddComponent = (newItem) => {
    setDraggedComponents((prev) => [...prev, newItem]);
  };

  return (
    <DndProvider backend={HTML5Backend}>
      <div style={{ display: 'flex' }}>
        <Sidebar onItemClick={handleSidebarItemClick} />
        <DropCanvas 
          canvasSize={canvasSize}
          draggedComponents={draggedComponents}
          DraggableItem={DraggableItem}
          onDrag={handleDrag}
          onAddComponent={handleAddComponent}
        />
      </div>
    </DndProvider>
  )
}

export default AAWhatsapp

额外注意事项

  • 所有使用useDrag或useDrop的组件,必须直接或间接被DndProvider包裹,且Hook调用必须在DndContext范围内。
  • 不要在同一个组件里既渲染DndProvider又调用react-dnd的Hook,因为组件执行Hook时,DndProvider还未完成挂载,上下文不存在。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 13:59:52