New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot have two HTML5 backends at the same time #186

Closed
prakhar1989 opened this Issue Jun 8, 2015 · 47 comments

Comments

Projects
None yet
@prakhar1989

prakhar1989 commented Jun 8, 2015

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?

@gaearon

This comment has been minimized.

Member

gaearon commented Jun 8, 2015

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?

@prakhar1989

This comment has been minimized.

prakhar1989 commented Jun 8, 2015

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?

@gaearon

This comment has been minimized.

Member

gaearon commented Jun 9, 2015

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).

@prakhar1989

This comment has been minimized.

prakhar1989 commented Jun 9, 2015

export <MyComponentContext> that applies DragDropContext(HTML5Backend)

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

@gaearon

This comment has been minimized.

Member

gaearon commented Jun 9, 2015

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.

@prakhar1989

This comment has been minimized.

prakhar1989 commented Jun 9, 2015

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.
@gaearon

This comment has been minimized.

Member

gaearon commented Jun 9, 2015

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.

@gaearon

This comment has been minimized.

Member

gaearon commented Jun 9, 2015

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.

@gaearon

This comment has been minimized.

Member

gaearon commented Jun 9, 2015

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} />
    }
}
@prakhar1989

This comment has been minimized.

prakhar1989 commented Jun 9, 2015

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

@gaearon

This comment has been minimized.

Member

gaearon commented Jun 9, 2015

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.

@prakhar1989

This comment has been minimized.

prakhar1989 commented Jun 9, 2015

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?

@gaearon

This comment has been minimized.

Member

gaearon commented Jun 9, 2015

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

@prakhar1989

This comment has been minimized.

prakhar1989 commented Jun 9, 2015

You had me for a second there! 😛

img

Thankfully it works!

@gaearon

This comment has been minimized.

Member

gaearon commented Jun 9, 2015

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

@gaearon gaearon closed this Jun 9, 2015

@prakhar1989

This comment has been minimized.

prakhar1989 commented Jun 9, 2015

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

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

@gaearon

This comment has been minimized.

Member

gaearon commented Jun 9, 2015

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

@EvHaus

This comment has been minimized.

EvHaus commented Jun 19, 2015

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;
    ...
  }
@prakhar1989

This comment has been minimized.

prakhar1989 commented Jun 19, 2015

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?

@abobwhite

This comment has been minimized.

abobwhite commented Feb 1, 2016

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.

@abobwhite

This comment has been minimized.

abobwhite commented Feb 3, 2016

@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.

@prakhar1989

This comment has been minimized.

prakhar1989 commented Feb 3, 2016

@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,

@abobwhite

This comment has been minimized.

abobwhite commented Feb 3, 2016

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...

@avk

This comment has been minimized.

avk commented Feb 11, 2016

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

@avk

This comment has been minimized.

avk commented Feb 11, 2016

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.

@broncha

This comment has been minimized.

broncha commented Jun 9, 2016

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.

@jcblw

This comment has been minimized.

jcblw commented Jun 13, 2016

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.

@martinnov92

This comment has been minimized.

martinnov92 commented Apr 5, 2017

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

edeustace added a commit to PieElements/corespring-placement-ordering that referenced this issue Apr 6, 2017

@mauron85

This comment has been minimized.

mauron85 commented May 3, 2017

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.

@noahehall

This comment has been minimized.

noahehall commented Jun 11, 2017

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

@andrewcapodieci

This comment has been minimized.

andrewcapodieci commented Jul 5, 2017

@gcorne's solution works

@szopenkrk

This comment has been minimized.

szopenkrk commented Jul 6, 2017

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?

@andresgutgon

This comment has been minimized.

andresgutgon commented Jul 6, 2017

Hi @szopenkrk did you tried this #186 (comment) ?
But I think you would need to do a pull request to react-big-calendar to accept a Dnd backend

@codeaid

This comment has been minimized.

codeaid commented Oct 10, 2017

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.

@martinnov92

This comment has been minimized.

martinnov92 commented Nov 9, 2017

@codeaid Thank you!

@guang2013

This comment has been minimized.

guang2013 commented Dec 13, 2017

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.

@andresgutgon

This comment has been minimized.

andresgutgon commented Dec 13, 2017

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

@guang2013

This comment has been minimized.

guang2013 commented Dec 13, 2017

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.

@CASandmann

This comment has been minimized.

CASandmann commented Jan 30, 2018

@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.

@jtbandes

This comment has been minimized.

jtbandes commented Aug 9, 2018

@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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment