React-dnd: Cannot have two HTML5 backends at the same time

Created on 8 Jun 2015  ·  62Comments  ·  Source: react-dnd/react-dnd

Hi Dan,

Just a quick one - I'm trying to use my own component that has react-dnd as a dependency in another app which _itself uses_ react-dnd so the error above expected. In this case, what would be the best way to fix this?

Since the other component is my own, I can remove the DragDropContext call from while exporting the component but then that sacrifices the re-usability of the component. What do you advise?

Most helpful comment

Another approach that is a bit cleaner is to create a module that generates the decorator for a particular backend, and then use the decorator where needed:

lib/withDragDropContext.js

import {DragDropContext} from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';

export default DragDropContext(HTML5Backend);

components/MyComponent.js

import { Component } from 'react';
import withDragDropContext from '../lib/withDnDContext';

class MyComponent extends Component {

  render() {
    return (
     <div>
       // other children
     </div>
   );
}

export default withDragDropContext(MyComponent);

All 62 comments

Check out the source for DragDropContext. I think you should be able to reuse the existing manager if it is specified by a component above the tree. But it's still a tricky question.. Do you have any proposed solutions?

I think you should be able to reuse the existing manager if it is specified by a component above the tree.

Even if this is possible, the component that is exported should be able to work independently as well, and in cases where the backend exists (in the app where the component is being used) somewhere up the chain should be reused. I'll try to read the source and see if this can be pulled off.

Unfortunately, the only solution I can think of right now, is to export the final component as it is, and expect the user to add a DragDropContext with a backend of their choosing. What do you think?

the component that is exported should be able to work independently as well, and in cases where the backend exists (in the app where the component is being used) somewhere up the chain should be reused.

Yes, this is possible by not using DragDropContext altogether and instead manually using dragDropManager in the context. Your component may look into context, and either pass the existing dragDropManager down the context, or create its own. It seems somewhat fragile though.

Unfortunately, the only solution I can think of right now, is to export the final component as it is, and expect the user to add a DragDropContext with a backend of their choosing. What do you think?

I think this is the most flexible solution. You can also be opinionated there and export <MyComponentContext> that applies DragDropContext(HTML5Backend).

export <MyComponentContext> that applies DragDropContext(HTML5Backend)

I'm sorry, I don't quite understand this. Can you clarify this a bit more?

export default function MyTagControlContext(DecoratedClass) {
  return DragDropContext(HTML5Backend)(DecoratedClass);
}

and you can tell users to either wrap their top-level component into MyTagControlContext or use DragDropContext directly if they _already_ use React DnD.

Ah! How about this? Does this look too ugly?

// in main component file
module.exports = {
    WithContext: DragDropContext(HTML5Backend)(ReactTags),
    WithOutContext: ReactTags
};

The usage can then be something like

var ReactTags = require('react-tags').WithContext; // if your app doesn't use react-dnd
var ReactTags = require('react-tags').WithOutContext; // if your app already uses react-dnd.

I don't think this would work because each <ReactTags> would get its own copy of backend and lead to the invariant error you pasted above because those backends handle the same global window events.

What I think will work is you can manually create dragDropManager (just like DragDropContext does internally) and use the same instance of it for all ReactTag instances—with a fallback to the manager defined in context.

I mean exporting something like this from your library:

let defaultManager;
function getDefaultManager() {
    if (!defaultManager) {
        defaultManager = new DragDropManager(HTML5Backend);
    }
    return defaultManager;
}

class ReactTagContext {
    static contextTypes = {
        dragDropManager: PropTypes.object.isRequired
    };

    static childContextTypes = {
        dragDropManager: PropTypes.object.isRequired
    };

    getChildContext() {
        return {
            dragDropManager: this.context.dragDropManager || getDefaultManager()
        };
    }

    render() {
        return <ReactTag {...props} />
    }
}

Thanks a lot, Dan! I'll try this out and get back to you. Thank you for sharing the code :grinning:

No prob.. If you do it like this, just export that class instead of exporting ReactTags directly. It should be usable "as is", without any wrappers or decorators.

So Dan! For the heck of it, I was trying out the multiple exports solution above -

// in main component file
module.exports = {
    WithContext: DragDropContext(HTML5Backend)(ReactTags),
    WithOutContext: ReactTags
};

In my other app, I tried importing the component without the context and much to my delight, it seems to be working fine!

Do you think this is a hacky solution and I should go ahead with what you've proposed or should I let this be?

@prakhar1989 Are you sure this works with multiple <Tags /> on the page?

You had me for a second there! :stuck_out_tongue:

img

Thankfully it works!

Hmm, then maybe it's fine ;-). Let me know if you have any issues with this approach!

Will do! Thanks a ton again for all your help.

PS: Do you have better naming ideas for WithContext and WithoutContext?

@prakhar1989 I'd probably just export WithContext version directly, and put NoContext as a static field on it.

I'm running into a similar issue and I'd like to better understand why this limitation exists in the first place because it's making writing re-usable components quite difficult. As it is, each component that uses react-dnd needs to be aware of various contexts that may exist in the application and deal with them accordingly. It would be preferable if each component could manage its own drag behaviour/context regardless of what else may be going on in the rest of the application.

For example, I may want to have an application screen which has several File Upload components, a sortable menu and a game with draggable elements. Each of those components have very different ways of dealing with drag events, and should really be in charge of their own context.

My first question, is why not simply do this inside the HTML5Backend code?

setup() {
    ...

    // Events already setup - do nothing
    if (this.constuctor.isSetUp) return;

    // Don't throw an error, just return above.
    //invariant(!this.constructor.isSetUp, 'Cannot have two HTML5 backends at the same time.');

    this.constructor.isSetUp = true;
    ...
  }

why not simply do this inside the HTML5Backend code

This is a great point. If the component is able to detect multiple backends can't it directly have the logic of falling back to the existing backend in scope?

Hi @gaearon - I am running into this issue as well except in my case, I have a page in which I've pieced together disparate react components within an angular template due to (angular) performance. What I have is a page which builds questions, choices, and more in a recursive tree structure. I also have a toolbar and a question library that use DnD to add things to the question tree. My problem is that I have now setup multiple react components that live within an angular context. Because of this, I am wrapping each of those with a DragDropContext which causes this error. I tried following the above thread but it's not entirely clear to me what I could do for these separate react components to share a context without converting everything else on my page to be React (not ideal). I am a bit familiar with the ES6 syntax but I am working on a project that is still using ES5. Is there a way to apply the shared DragDropManager concept from above? I've tried it so far and I don't seem to have access to DragDropManager since it's in dnd-core

Thanks for your help and for this awesome library!

P.S. If it matters, I'm using ngReact.

@prakhar1989 @globexdesigns @gaearon

I am wondering the same thing why HTML5 backend can't just re-use the backend if multiple are used? Per my previous comment, this is really making react-dnd unusable for me as I have multiple react areas within an angular page that need to be able to DnD between each other and am hitting a wall with this.

Anyone have any quick fix for this? I'm at a dead stop in my development.

@abobwhite This is how I've _solved_ it. It's definitely not a great solution but it seems to work as of now.

Hope this helps,

Thanks, @prakhar1989 ! But I'm not following how the multiple export with one wrapped with context and one not solves the issue. My problem is not being potentially embedded into another application with react-dnd but rather not being able to wrap my entire dnd-enabled area (with several react and angular components/directives) in react so I was trying to wrap the context around just those react components in my page that support DnD...I'd love to try @gaearon 's approach from above but I don't have access to the DragDropManager in order to new one up...

I'm having the exact same issue. I strongly agree with @abobwhite that this makes components less reusable.

This thread led me to solving my invariant problem by moving the HTML5Backend and DragDropContext higher in my component hierarchy, just like the docs recommend.

This is a weird problem. I am working on a nested reorderable component and I have DragDropContext nested inside the parent one. It seems to work standalone (but it still has nested DragDropContext).

But when I use that component inside another project which has DragDropContext initialised above the hierarchy, I get this error.

I ran into this issue with an application I'm working on. I had full control over all the components so I ended up instead of using @DragDropContext(HTMLBackend) I ended up using something very close to @gaearon code in this comment to create a decorator that would give a shared drag drop context. It works really well.

I just had the same thing happen to me and the problem was that I had ComponentA which used @DragDropContext(HTMLBackend) and then ComponentB which also had @DragDropContext(HTMLBackend).

ComponentB was imported in ComponentA which caused the error for me. All I had to do was to remove the DragDropContext from ComponentB and it worked.

What if there are 2 DrapDropContext in the app but they are not parent and child?

My case:

  1. Intialize first dnd Context
  2. Initialize as sibbling a second dnd Context. 💣

I can't check on childContext because second component is not a child of first dnd component/context

To solve my problem I did a singleton with this code:

import { DragDropManager } from 'dnd-core';
import HTML5Backend from 'react-dnd-html5-backend';

let defaultManager;

/**
 * This is singleton used to initialize only once dnd in our app.
 * If you initialized dnd and then try to initialize another dnd
 * context the app will break.
 * Here is more info: https://github.com/gaearon/react-dnd/issues/186
 *
 * The solution is to call Dnd context from this singleton this way
 * all dnd contexts in the app are the same.
 */
export default function getDndContext() {
  if (defaultManager) return defaultManager;

  defaultManager = new DragDropManager(HTML5Backend);

  return defaultManager;
}

And then in all the components that have a child that had DragDropContext(HTML5Backend) I remove it from those child and in their parents I do this:

import getDndContext from 'lib/dnd-global-context';

const ParentComponent = React.createClass({

  childContextTypes: {
    dragDropManager: React.PropTypes.object.isRequired,
  },

  getChildContext() {
    return {
      dragDropManager: getDndContext(),
    };
  },

  render() {
    return (<ChildComponentWithDndContext />);
  },

I think the key is that I only initialize dnd context once. What do you think?

@andresgutgon thanks. It works for me too

@andresgutgon you solution is great, but it's weird that DrapDropContext doesn't destroy DragDropManager on componentWillUnmount, i.e. if you don't use 2 DrapDropContexts at the same time, but rather on 2 different pages -- you still cannot use them. I'm going to try your hack in my case, but it's still 100% weird to even have hacks for such trivial situation.

Also any idea why having 2 DragDropManager is something bad/wrong in react-dnd?

Another approach that is a bit cleaner is to create a module that generates the decorator for a particular backend, and then use the decorator where needed:

lib/withDragDropContext.js

import {DragDropContext} from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';

export default DragDropContext(HTML5Backend);

components/MyComponent.js

import { Component } from 'react';
import withDragDropContext from '../lib/withDnDContext';

class MyComponent extends Component {

  render() {
    return (
     <div>
       // other children
     </div>
   );
}

export default withDragDropContext(MyComponent);

Hello, does anybody knows how to solve when you have one component wrapped in DragDropContext and then another component which uses DragDropContext as well, but this component (react big calendar) is an npm package, so remove it from there is not a solution and they are next to each other, not parent-child... you can have a look here https://github.com/martinnov92/TSCalendar (it is work in progress - so it is little bit messy :D) thank you

I believe I've exactly same issue with using react-mosaic tiling window manager which is using react-dnd and my own components, that are also using react-dnd.

So I'm guessing this in not solved directly in react-dnd, but there are some workarounds we can apply.

is there an example of a full working code that fixes this issue?

@gcorne's solution works

Hi i have same problem but we use react-data-grid and try to add react-big-calendar
After add component which include react-big-calendar we have error "Uncaught Error: Cannot have two HTML5 backends at the same time."

react-big-calendar use react-dnd and react-dnd-html5-backend how to solve that problem?

Hi @szopenkrk did you tried this https://github.com/react-dnd/react-dnd/issues/186#issuecomment-232382782 ?
But I think you would need to do a pull request to react-big-calendar to accept a Dnd backend

Probably a bit too late but I've come up with a similar but slightly different solution. I implemented a higher order component and simply use it in all my DragDropContext aware components.

Code looks like this (TypeScript):

import * as React from 'react';
import {DragDropContext} from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';

// context singleton
let context: Function;

export function withDragDropContext<P>(
    Component: React.ComponentClass<P> | React.StatelessComponent<P>,
): React.ComponentClass<P> {
    // ensure a singleton instance of the context exists
    if (!context) {
        context = DragDropContext<P>(HTML5Backend);
    }

    return context(Component);
}

And then use it as follows in your components:

import * as React from 'react';
import {withDragDropContext} from 'components/WithDragDropContext';

class MyClass extends React.Component<IMyClassProps, {}> {
    // ...
}

export default withDragDropContext<IMyClassProps>(MyClass);

N.B.
I've not tried yet but you should probably be able to populate the context variable during the declaration:

const context = DragDropContext(HTML5Backend);

and then skip the if (!context) {... part.

@codeaid Thank you!

I met the same situation as @andresgutgon , but could not solve with his method and all the method here I tried and no one works, is there anybody can help me? Thank you.

Why didn't work @guang2013 any of the solutions here?

No, still no solutions, I don't know, I tried to drag the card into bigCalendar, and I made the card as dragSource and the cardsList as a DragAndDropContext, the calendar as another DragAndDropContext, then two html5backend errors throw out, I tried to use any method provided here but no one can solve my problem. @andresgutgon , when are you online, can I talk to you directly on this? Much appreciated.

@guang2013 if you are using a library that depends on DragDropContext from react-dnd, this technique won't work because the library won't use your unified dndContext. Some libraries like react-sortable-tree will allow you to use the components without a context so you can wrap them yourself though.

@gcorne's solution worked for me to allow using react-dnd in an app with react-hot-loader. Still, I'm a little surprised it didn't work out of the box!

Old issue, but in case somebody else ends up here from Google:

I had only 1 DND provider on my page, I was not integrating any other library that has DND, but I still ended up running into this error, somewhat randomly.

My problem was that the DragDropContextProvider was inside ReactRouter's BrowserRouter element, which made the HTML5Backend to be reconstructed on every navigation, and if both the old page (that was navigated away from) and the new one (the one navigated to) had DND elements, the above error would occur.

The solution was to move the DragDropContextProvider out of BrowserRouter.

This one is for those mortals who tried out ReactDnD for the first time, followed the tutorial and ended up with a 64 square chessboard.
I could drag my knight around as I pleased.
Problem was when I tried to drop it in one of the BoardSquare, it threw up with the 2 HTML5 backend issue.

The Fix

As others have mentioned in comments before, move the DragDropContextProvider rendering outside the app rerender cycle.
As in, don't directly do a ReactDOM.render as a callback to the observe function.
Instead do this:

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { DragDropContextProvider } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import './index.css';
import App from './App';

ReactDOM.render(
    <DragDropContextProvider backend={HTML5Backend}>
        <App />
    </DragDropContextProvider>,
    document.getElementById('root')
)

App.js

import React, { Component } from 'react';
import Board from './Board';
import { observe } from './Game';

class App extends Component {
    state = {
        knightPosition: [0, 0]
    }

    componentDidMount = () => {
        observe(knightPosition => {
            this.setState(prevState => ({
                ...prevState,
                knightPosition
            }));
        });
    }

    render() {
        return (
            <div className="App">
                <Board knightPosition={this.state.knightPosition} />
            </div>
        );
    }
}

export default App;

I think this is related to this issue
https://github.com/prakhar1989/react-tags/issues/497

@gaearon I know this is old, but how do I actually get the hold of DragDropManager in your example? It's not exported anywhere.

defaultManager = new DragDropManager(HTML5Backend);

My problem is that I'm rendering a few widgets on a page via some 3rd party API, and I can't move DragDropContextProvider higher than my actual widget.

Solved this issue by deleting the old build.
just delete the dist folder or edit index.html. I don't know the exact problem but this worked for me

Fortunately, I could find the (hidden) answer of @gcorne (https://github.com/react-dnd/react-dnd/issues/186#issuecomment-282789420). It solved my problem - which was assumed to be tricky - instantaneously.
@prakhar1989 I have the feeling it is a real party answer and that it should be highlighted somehow, so maybe link it in the bug description?

The solution I figured out that works for me and allows me to use either HTML5 or Touch backend is:

Create a singleton HOC component:

import {DragDropContext} from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend";
import TouchBackend from "react-dnd-touch-backend";

const DragAndDropHOC = props => {
    return <React.Fragment>
        {props.children}
    </React.Fragment>
};

export default {
    HTML5: DragDropContext(HTML5Backend)(DragAndDropHOC),
    Touch: DragDropContext(TouchBackend)(DragAndDropHOC),
}

Then a provider component:

const DragDrop = props => {
    if (props.isTouch) {
        return <DragDropContext.Touch>{props.children}</DragDropContext.Touch>
    } else {
        return <DragDropContext.HTML5>{props.children}</DragDropContext.HTML5>
    }
};

And use <DragDrop isTouch={props.isTouch} /> wherever I need it.

For developers who come across the same issue, you can take a look at this HOC solution

I am running into this problem now with Jest tests. HTML5-Backend is treated like a singleton across the Jest tests (which is why the problem is happening...i think)

Detailed problem in SO :

https://stackoverflow.com/questions/58077693/multiple-react-dnd-jest-tests-cannot-have-two-html5-backends-at-the-same-time

Using hooks

import { createDndContext } from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend";

const manager = useRef(createDndContext(HTML5Backend));

return (
  <DndProvider manager={manager.current.dragDropManager}>
      ....
  </DndProvider>
)

A better solution using Hooks (thanks @jchonde):

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>;
}

so then you can use elsewhere:

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

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

My solution:
Make the children component don't import react-dnd directly.
Pass the DragDropContext and HTML5Backend from parent to children component.

DragDropContextProvider标签里加一个不重复的key就解决了<DragDropContextProvider backend={HTML5Backend} key={Math. random()}></DragDropContextProvider>

In the typescript, i made the below component. (thanks @jchonde @ttessarolo )

import { DndProvider, createDndContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import React, { PropsWithChildren, useRef } from 'react';

const RNDContext = createDndContext(HTML5Backend);

function DragAndDrop({ children }: PropsWithChildren<{}>): JSX.Element {
  const manager = useRef(RNDContext);
  return <DndProvider manager={manager.current.dragDropManager}>{children}</DndProvider>;
}

export default DragAndDrop;

And used a component like this

function SomeComponent(): JSX.Element {
  return (
    <DragAndDrop>
      ...
    </DragAndDrop>
  );
}

DragDropContextProvider标签里加一个不重复的key就解决了<DragDropContextProvider backend={HTML5Backend} key={Math. random()}></DragDropContextProvider>

good job!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jsteel picture jsteel  ·  7Comments

KyleAMathews picture KyleAMathews  ·  25Comments

muratsu picture muratsu  ·  29Comments

dshorowitz picture dshorowitz  ·  15Comments

jacobp100 picture jacobp100  ·  43Comments