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 我通过为document.getElementById
时,没有使用您在 div
元素上设置的 id
渲染 DOM,因此 context
的结果始终为空。我在P.S.
下用粗体添加了这个“问题”以避免这个陷阱。最后,我看到你基本上按照我的建议做了。为DndProvider
添加了rootElement
。我将更新我的 sn-p 以解决这个陷阱。谢谢。以上是关于React DnD - “不能同时拥有两个 HTML5 后端。”的主要内容,如果未能解决你的问题,请参考以下文章
Dragabbale 不会在 react-beautiful-dnd 中移动
如何使用 react-dnd 从材质 UI 中拖放 TableHeaderColumn?