react-dnd: Multiple DndProviders inside a pure component can lead to `Cannot have two HTML5 backends at the same time`

Describe the bug

Using two <DndProvider backend={HTML5Backend}> inside a React.PureComponent (which updates only if props change), can under certain conditions lead to an error like:

Error: Cannot have two HTML5 backends at the same time.
	    at HTML5BackendImpl.setup node_modules/react-dnd-html5-backend/dist/esm/HTML5BackendImpl.js:423
	    at DragDropManagerImpl.handleRefCountChange node_modules/dnd-core/dist/esm/classes/DragDropManagerImpl.js:21
	    at Object.dispatch node_modules/redux/es/redux.js:297
	    at HandlerRegistryImpl.addSource node_modules/dnd-core/dist/esm/classes/HandlerRegistryImpl.js:92
	    at registerSource node_modules/react-dnd/dist/esm/internals/registration.js:10
	    at registerDragSource node_modules/react-dnd/dist/esm/hooks/useDrag/useRegisteredDragSource.js:24
	    at commitHookEffectListMount node_modules/react-dom/cjs/react-dom.development.js:20573
	    at commitLifeCycles node_modules/react-dom/cjs/react-dom.development.js:20634
	    at commitLayoutEffects node_modules/react-dom/cjs/react-dom.development.js:23426
	    at HTMLUnknownElement.callCallback node_modules/react-dom/cjs/react-dom.development.js:3945
	    at Object.invokeGuardedCallbackDev node_modules/react-dom/cjs/react-dom.development.js:3994
	    at invokeGuardedCallback node_modules/react-dom/cjs/react-dom.development.js:4056
	    at commitRootImpl node_modules/react-dom/cjs/react-dom.development.js:23151
	    at unstable_runWithPriority node_modules/scheduler/cjs/scheduler.development.js:468
	    at runWithPriority$1 node_modules/react-dom/cjs/react-dom.development.js:11276
	    at commitRoot node_modules/react-dom/cjs/react-dom.development.js:22990

Reproduction

This is the smallest reproducing example I was able to come up with: https://codepen.io/dcsaszar/pen/ZEKLVVJ

It describes basically what happens in one of our apps: Inside a PureComponent-like component (we have our own abstraction, but with a similar implementation of shouldComponentUpdate), we have a single read-only (this is a prop, in the example: foo) DnD component, which later is dynamically replaced by a single read-write DnD component, and then joined by a 2nd DnD component.

Steps to reproduce the behavior:

  1. Go to https://codepen.io/dcsaszar/pen/ZEKLVVJ
  2. See the error in the browser console

Expected behavior

  • No error.
  • The components should render successfully.

Desktop

  • Browser: Chrome
  • Version:
    • react 17.0.2
    • react-dnd: 14.0.2
    • react-dnd-html5-backend: 14.0.0

Related #1558 #3119 #3178

Since I wasn’t sure which/if any of the above qualifies for duplicate, I created a fresh issue.

About this issue

Commits related to this issue

Most upvoted comments

add props context={window}, it’s solved my problem.

For people still struggling with this: the issue mostly seems to occur when you wrap your DnD enabled component directly with a DnDProvider (instead of using the Provider at the top level of your app) and then navigating between routes/pages in your app.

I see two workarounds:

  • Wrap your top level <App> component with the <DnDProvider> and remove any other DnDProvider components. This ensures there can only ever be a single instance of the HTML5 backend.
  • Add the context={window} prop on the provider <DndProvider backend={HTML5Backend} context={window}>.