react-dnd
react-dnd copied to clipboard
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:
- Go to https://codepen.io/dcsaszar/pen/ZEKLVVJ
- 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.
+1 Getting the same error
I am using version 7.7.0 for both react-dnd and react-dnd-html5-backend
Cannot have two HTML5 backends at the same time.
206 |
> 207 | const wrapper = mount(
| ^
208 | <Provider store={store}>
209 | <DragDropContextProvider backend={HTML5Backend}>
210 | <MyComponent />
at HTML5Backend.Object.<anonymous>.HTML5Backend.setup (node_modules/react-dnd-html5-backend/lib/cjs/HTML5Backend.js:318:19)
at DragDropManagerImpl.handleRefCountChange (node_modules/dnd-core/lib/cjs/DragDropManagerImpl.js:30:31)
Got the same issue with
"react-dnd": "16.0.0", "react-dnd-html5-backend": "16.0.0",
add props context={window}, it's solved my problem.
add props context={window}, it's solved my problem.
Thanks you saved my ass
thx
add props context={window}, it's solved my problem too. with version: "react-dnd": "^9.4.0", "react-dnd-html5-backend": "^9.4.1",
should this context prop be documented somehow ?
should this be true by default ?
const isGlobalInstance = !props.context
https://github.com/react-dnd/react-dnd/blob/7c88c37489a53b5ac98699c46a506a8e085f1c03/packages/react-dnd/src/core/DndProvider.tsx#L65
add props context={window}, it's solved my problem.
thx,it works
Got the same issue with
"react-dnd": "16.0.0", "react-dnd-html5-backend": "16.0.0",
Have you solved this problem yet? My version is "react-dnd": "^14.0.4", "react-dnd-html5-backend": "^14.0.2",
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 otherDnDProvidercomponents. 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}>.
Thanks a lot @ekeijl . Below solution worked for me.
Add the context={window} prop on the provider <DndProvider backend={HTML5Backend} context={window}>
add props context={window}, it's solved my problem.
thx,dude!
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 otherDnDProvidercomponents. 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}>.
Hi @ekeijl , this context={window} resolves the issue. but why we have passed window? can we pass something else here?
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 otherDnDProvidercomponents. 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}>.Hi @ekeijl , this
context={window}resolves the issue. but why we have passedwindow? can we pass something else here?
My guess is that context can be any globally available object (like window), but I have not tried it.
add props context={window}, it's solved my problem.
Amazing, this worked for me too! Thank you @ymh1028
The docs are pretty terrible and outdated. So it's probably unlikely they'll be updated to include this, but at least this workaround exists.
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.
This approach probably would work if you didn't have multiple sections in your App to have independent DnD interactions. In my case, we don't ever want a single DnD provider. We have multiple widgets within our App that all have their own DnD capabilities that shouldn't cross boundaries into other DnD areas.
So I wouldn't recommend this as a potential solution here b/c it shouldn't be the case to have only a single dnd provider at the root.
I did it.
I did it.
Using random number as key can really impact performance. For React reconciliation algorithm it will be new component on each render and it will recreate it, instead of reuse existing one, and probably that is why it works for you.
