React DnD - “不能同时拥有两个 HTML5 后端。”

Posted

技术标签:

【中文标题】React DnD - “不能同时拥有两个 HTML5 后端。”【英文标题】:React DnD - "Cannot have two HTML5 backends at the same time." 【发布时间】:2017-09-16 20:58:33 【问题描述】:

我正在尝试使用 Rails5、动作 Cable、React 和 Rails 以及 React DnD 制作 POC。

目的是制作像 trello 这样的应用,但用于招聘流程。

我的前面是 ReactJS。

我有3个组件,首先,容器调用“Candidates”,这个组件调用2个“CardBoard”组件,调用“Card”组件。

我用户对可拖动卡片和可放置 CardBoard 的 DnD 库做出反应。当我将卡片放在纸板上时,我使用 post call 和 websocket(来自 rails5 的操作电缆)来更新我的状态。我不明白为什么我在通话后收到此消息:

Uncaught Error: Cannot have two html5 backends at the same time.
    at HTML5Backend.setup (eval at <anonymous> (webpack-bundle.self-7b1a342….js?body=1:4175), <anonymous>:87:15)
    at DragDropManager.handleRefCountChange (eval at <anonymous> (webpack-bundle.self-7b1a342….js?body=1:3566), <anonymous>:52:22)
    at Object.dispatch (eval at <anonymous> (webpack-bundle.self-7b1a342….js?body=1:4931), <anonymous>:186:19)
    at HandlerRegistry.addSource (eval at <anonymous> (webpack-bundle.self-7b1a342….js?body=1:3594), <anonymous>:104:18)
    at registerSource (eval at <anonymous> (webpack-bundle.self-7b1a342….js?body=1:4294), <anonymous>:9:27)
    at DragDropContainer.receiveType (eval at <anonymous> (webpack-bundle.self-7b1a342….js?body=1:1793), <anonymous>:146:32)
    at DragDropContainer.receiveProps (eval at <anonymous> (webpack-bundle.self-7b1a342….js?body=1:1793), <anonymous>:135:14)
    at new DragDropContainer (eval at <anonymous> (webpack-bundle.self-7b1a342….js?body=1:1793), <anonymous>:102:13)
    at eval (eval at <anonymous> (webpack-bundle.self-7b1a342….js?body=1:4399), <anonymous>:295:18)
    at measureLifeCyclePerf (eval at <anonymous> (webpack-bundle.self-7b1a342….js?body=1:4399), <anonymous>:75:12)

候选人.jsx =

import React,  PropTypes  from 'react';
import  DragDropContextProvider  from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import CardBoard from './CardBoard.jsx';

export default class Candidates extends React.Component 

  constructor(props, _railsContext) 
    super(props);

    this.state = 
      candidates: this.props.candidates
    

    this.filterByStatus = this.filterByStatus.bind(this)
  

  componentDidMount() 
    this.setupSubscription();
  

  setupSubscription() 
    App.candidate = App.cable.subscriptions.create("CandidateChannel", 
      connected: () => 
        console.log("User connected !")
      ,

      received: (data) => 
        this.setState( candidates: data.candidates )
      ,
    );
   

  render() 
    return (
      <DragDropContextProvider backend=HTML5Backend>
        <div className="recruitment">
          
            ["À Rencontrer", "Entretien"].map((status, index) => 
              return (
                <CardBoard candidates=(this.filterByStatus(status)) status=status key=index />
              );
            )
          
        </div>
      </DragDropContextProvider>
    );
   

CardBoard.jsx =

import React,  PropTypes  from 'react';
import Card from './Card.jsx';
import  DropTarget  from 'react-dnd';
import ItemTypes from './ItemTypes';

const cardTarget = 
  drop(props: Props) 
    var status = ''

    if(props.status == "À Rencontrer") 
      status = 'to_book'
     else 
      status = 'interview'
    

    return  status: status ;
  ,
;

@DropTarget(ItemTypes.CARD, cardTarget, (connect, monitor) => (
  connectDropTarget: connect.dropTarget(),
  isOver: monitor.isOver(),
  canDrop: monitor.canDrop(),
))

export default class CardBoard extends React.Component<Props> 

  constructor(props, _railsContext) 
    super(props);
  

  render() 
    const  canDrop, isOver, connectDropTarget  = this.props;
    const isActive = canDrop && isOver;


    return connectDropTarget(
      <div className=`$this.props.status ui cards`>
        <h2>`$this.props.status ($this.props.candidates.length)`</h2>
        
          (this.props.candidates).map((candidate, index) => 
            return <Card candidate=candidate key=index />
          )
        
         isActive?
          'Release to drop' : 'drag a card here'
        
      </div>
    );
  

Card.jsx=

import React,  PropTypes  from 'react';
import  DragSource  from 'react-dnd';
import ItemTypes from './ItemTypes';


const cardSource = 
  beginDrag(props) 
    return 
      candidate_id: props.candidate.id,
    ;
  ,

  endDrag(props, monitor) 
    const item = monitor.getItem();
    const dropResult = monitor.getDropResult();

    if (dropResult) 
      console.log(`You dropped $item.candidate_id vers $dropResult.status !`);
      $.post(`/update_status/$item.candidate_id/$dropResult.status`);
    
  ,
;

@DragSource(ItemTypes.CARD, cardSource, (connect, monitor) => (
  connectDragSource: connect.dragSource(),
  isDragging: monitor.isDragging(),
))

export default class Card extends React.Component 

  constructor(props, _railsContext) 
    super(props);
  

  render() 
    const  isDragging, connectDragSource  = this.props;
    const  name  = this.props;
    const opacity = isDragging ? 0 : 1;

    var candidate = this.props.candidate;

    return (
      connectDragSource(
        <div className="card" key=candidate.id style=opacity>
          <div className="content">
            <div className="header">`$candidate.first_name $candidate.last_name`</div>
            <div className="description">
              candidate.job_title
            </div>
            <span className="right floated">
              <i className="heart outline like icon"></i>
              candidate.average_rate
            </span>
          </div>
        </div>
      )
    );
  

为了更好地理解,这里是我的功能和他的错误的 gif: feature with bug

here my github repo with all code

【问题讨论】:

你试过用 DragDropContext 代替 DragDropContextProvider 吗? 【参考方案1】:

尝试将HTML5Backend 的上下文设为单例。您可以在以下位置查看代码:

https://github.com/react-dnd/react-dnd/issues/186

https://github.com/react-dnd/react-dnd/issues/708

【讨论】:

这应该是未来任何人的答案【参考方案2】:

我遇到了类似的问题,并通过移动找到了解决方法

<DragDropContextProvider></DragDropContextProvider>

到最顶层的反应组件。

之前的结构:

  App
    Component1
      Component2
        ...
        ComponentX with render(<DragDropContextProvider>...<DragDropContextProvider>)

之后的结构:

  App with render(<DragDropContextProvider>...<DragDropContextProvider>)
    Component1 
      Component2
        ...
        ComponentX 

假设 App 只加载一次,并且没有复制 HTML5 后端。

【讨论】:

【参考方案3】:

您可以创建一个新文件并将DragDropContext 导入您需要的位置:

import HTML5 from 'react-dnd-html5-backend';
import DragDropContext from 'react-dnd';
export default DragDropContext(HTML5);

来源:https://github.com/react-dnd/react-dnd/issues/708#issuecomment-309259695

或者使用 Hooks:

import  DndProvider, createDndContext  from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend";
import React,  useRef  from "react";

const RNDContext = createDndContext(HTML5Backend);

function useDNDProviderElement(props) 
  const manager = useRef(RNDContext);

  if (!props.children) return null;

  return <DndProvider manager=manager.current.dragDropManager>props.children</DndProvider>;


export default function DragAndDrop(props) 
  const DNDElement = useDNDProviderElement(props);
  return <React.Fragment>DNDElement</React.Fragment>;

用途:

import DragAndDrop from "../some/path/DragAndDrop";

export default function MyComp(props)
   return <DragAndDrop>....<DragAndDrop/>

来源:https://github.com/react-dnd/react-dnd/issues/186#issuecomment-561631584

如果您使用BrowserRouter,则将DragDropContextProvider 移出BrowserRouter

来源:https://github.com/react-dnd/react-dnd/issues/186#issuecomment-453990887

【讨论】:

【参考方案4】:

DndProvider 有一个 options 属性,您可以在其中设置 rootElement 将 DnD 绑定到指定的上下文,不幸的是它没有很好地记录。这种方法解决了我所有的问题,因为我有其他使用 DnD 的组件,它们超出了我的范围,我无法制作 HTML5Backend Singleton。 我用"react-dnd": "^14.0.2"尝试了这种方法

const myFirstId = 'first-DnD-Containier';
const mySecondId = 'second-DnD-Containier';

export const DndWrapper = React.memo((props) => 
  const [context, setContext] = useState(null);

  useEffect(() => 
    setContext(document.getElementById(props.id))
  ,[props.id])

  return context ? (
      <DndProvider backend=HTML5Backend options= rootElement: context>
          props.children
      </DndProvider>
  ) : null;
);

export default function App() 
  return (
    <div>
      <div id=myFirstId>
        <DndWrapper id=myFirstId>
               <MyOtherComponents /> 
        </DndWrapper>
      </div>
      <div id=mySecondId>
          <DndWrapper id=mySecondId>
            <MyOtherComponents />
          </DndWrapper>
      </div>
    </div>
  );


附注确保在您调用 document.getElementById 时,DOM 已经存在并带有 id。

【讨论】:

嗨,@khserver,我遇到了类似的问题(github.com/react-dnd/react-dnd/issues/3344)并使用了您上面推荐的解决方案,但似乎仍然无法正常工作,请设置 rootElement。密码箱为codesandbox.io/s/elastic-bhabha-react-dnd-9sews?file=/src/…,调试状态截图为sm.ms/image/EQUoN38SCejnqch 我通过为 指定根元素解决了这个问题。更多详情请看issuegithub.com/react-dnd/react-dnd/issues/3344 嗨@Orionpax,我查看了您附加的沙箱。在您的示例中使用我的 sn-p 的问题是,当调用 document.getElementById 时,没有使用您在 div 元素上设置的 id 渲染 DOM,因此 context 的结果始终为空。我在P.S. 下用粗体添加了这个“问题”以避免这个陷阱。最后,我看到你基本上按照我的建议做了。为DndProvider 添加了rootElement。我将更新我的 sn-p 以解决这个陷阱。谢谢。

以上是关于React DnD - “不能同时拥有两个 HTML5 后端。”的主要内容,如果未能解决你的问题,请参考以下文章

react-dnd 拖拽 九宫格

Dragabbale 不会在 react-beautiful-dnd 中移动

react-dnd 中的动画

如何使用 react-dnd 从材质 UI 中拖放 TableHeaderColumn?

如何解决“react-dnd-html5-backend”不包含默认导出?

如何在 react-dnd-treeview 库上使用 Selenium 测试拖放