【问题标题】:React DnD - "Cannot have two HTML5 backends at the same time."React DnD - “不能同时拥有两个 HTML5 后端。”
【发布时间】: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 吗?

标签: reactjs drag-and-drop ecmascript-6 react-jsx react-dnd


【解决方案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 以解决这个陷阱。谢谢。
      猜你喜欢
      • 2020-01-24
      • 2018-08-17
      • 1970-01-01
      • 2018-10-13
      • 1970-01-01
      • 2018-01-16
      • 1970-01-01
      • 2022-06-29
      • 1970-01-01
      相关资源
      最近更新 更多