React-dnd: Não pode ter dois back-ends HTML5 ao mesmo tempo

Criado em 8 jun. 2015  ·  62Comentários  ·  Fonte: react-dnd/react-dnd

Oi dan,

Só um rápido - estou tentando usar meu próprio componente que tem react-dnd como uma dependência em outro aplicativo que _se usa_ react-dnd então o erro acima era esperado. Nesse caso, qual seria a melhor forma de corrigir isso?

Como o outro componente é meu, posso remover a chamada DragDropContext durante a exportação do componente, mas isso sacrifica a reutilização do componente. O que você aconselha?

Comentários muito úteis

Outra abordagem que é um pouco mais limpa é criar um módulo que gere o decorador para um determinado back-end e, em seguida, usar o decorador quando necessário:

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

Todos 62 comentários

Verifique a fonte de DragDropContext . Acho que você deve ser capaz de reutilizar o gerenciador existente se for especificado por um componente acima da árvore. Mas ainda é uma questão complicada. Você tem alguma solução proposta?

Acho que você deve ser capaz de reutilizar o gerenciador existente se for especificado por um componente acima da árvore.

Mesmo se isso for possível, o componente que é exportado deve ser capaz de funcionar de forma independente também e, nos casos em que o back-end existe (no aplicativo em que o componente está sendo usado) em algum lugar acima da cadeia, deve ser reutilizado. Vou tentar ler a fonte e ver se isso pode ser feito.

Infelizmente, a única solução que posso pensar agora é exportar o componente final como está e esperar que o usuário adicione DragDropContext com um back-end de sua escolha. O que você acha?

o componente exportado também deve ser capaz de funcionar de forma independente e, nos casos em que o back-end existe (no aplicativo em que o componente está sendo usado), em algum lugar acima da cadeia, deve ser reutilizado.

Sim, isso é possível não usando DragDropContext completamente e, em vez disso, usando manualmente dragDropManager no contexto. Seu componente pode examinar o contexto e passar o dragDropManager pelo contexto ou criar o seu próprio. Mas parece um tanto frágil.

Infelizmente, a única solução que posso pensar agora é exportar o componente final como está e esperar que o usuário adicione um DragDropContext com um back-end de sua escolha. O que você acha?

Acho que essa é a solução mais flexível. Você também pode ser opinativo e exportar <MyComponentContext> que se aplica DragDropContext(HTML5Backend) .

exportar <MyComponentContext> que se aplica DragDropContext(HTML5Backend)

Desculpe, não entendo muito bem. Você pode esclarecer isso um pouco mais?

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

e você pode dizer aos usuários para embrulhar seu componente de nível superior em MyTagControlContext ou usar DragDropContext diretamente se eles _já_ usarem React DnD.

Ah! Que tal agora? Isso parece muito feio?

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

O uso pode ser algo como

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.

Eu não acho que isso funcionaria porque cada <ReactTags> obteria sua própria cópia do back-end e levaria ao erro invariável que você colou acima, porque esses back-ends lidam com os mesmos eventos globais de janela.

O que eu acho que funcionará é que você pode criar manualmente dragDropManager (assim como DragDropContext internamente) e usar a mesma instância dele para todas as instâncias de ReactTag - com um fallback para o gerente definido em context .

Quero dizer, exportar algo assim da sua biblioteca:

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

Muito obrigado, Dan! Vou tentar isso e retorno para você. Obrigado por compartilhar o código: sorrindo:

Sem problemas. Se você fizer assim, apenas exporte aquela classe em vez de exportar ReactTags diretamente. Deve ser utilizável "como está", sem invólucros ou decoradores.

Então Dan! Por falar nisso, eu estava experimentando a solução de múltiplas exportações acima -

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

Em meu outro aplicativo, tentei importar o componente sem o contexto e, para minha alegria, parece estar funcionando bem!

Você acha que esta é uma solução hacky e devo prosseguir com o que você propôs ou devo deixar que assim seja?

@ prakhar1989 Tem certeza de que funciona com vários <Tags /> na página?

Você me pegou por um segundo lá! : stick_out_tongue:

img

Felizmente, funciona!

Hmm, então talvez esteja tudo bem ;-). Entre em contato se tiver problemas com essa abordagem.

Vai fazer! Muito obrigado novamente por toda sua ajuda.

PS: Você tem melhores ideias de nomes para WithContext e WithoutContext ?

@ prakhar1989 Eu provavelmente apenas exportaria WithContext versão diretamente e colocaria NoContext como um campo estático nele.

Estou tendo um problema semelhante e gostaria de entender melhor por que essa limitação existe, porque ela está dificultando muito a escrita de componentes reutilizáveis. Como está, cada componente que usa react-dnd precisa estar ciente dos vários contextos que podem existir no aplicativo e lidar com eles de acordo. Seria preferível se cada componente pudesse gerenciar seu próprio comportamento / contexto de arrastar, independentemente do que mais esteja acontecendo no restante do aplicativo.

Por exemplo, posso querer uma tela de aplicativo que tenha vários componentes de upload de arquivo, um menu classificável e um jogo com elementos arrastáveis. Cada um desses componentes tem maneiras muito diferentes de lidar com eventos de arrasto e deve ser responsável por seu próprio contexto.

Minha primeira pergunta é por que não fazer isso simplesmente dentro do código HTML5Backend?

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

por que não fazer isso simplesmente dentro do código HTML5Backend

Este é um ótimo ponto. Se o componente é capaz de detectar vários back-ends, ele não pode ter a lógica direta de retroceder para o back-end existente no escopo?

Olá @gaearon - Também estou encontrando esse problema, exceto no meu caso, tenho uma página na qual reuni componentes de reação díspares em um modelo angular devido ao desempenho (angular). O que tenho é uma página que cria perguntas, escolhas e muito mais em uma estrutura de árvore recursiva. Eu também tenho uma barra de ferramentas e uma biblioteca de perguntas que usa DnD para adicionar coisas à árvore de perguntas. Meu problema é que agora configurei vários componentes de reação que vivem em um contexto angular. Por causa disso, estou envolvendo cada um deles com um DragDropContext que causa este erro. Eu tentei seguir o tópico acima, mas não está totalmente claro para mim o que eu poderia fazer para que esses componentes de reação separados compartilhassem um contexto sem converter tudo o mais em minha página para React (não ideal). Estou um pouco familiarizado com a sintaxe ES6, mas estou trabalhando em um projeto que ainda está usando ES5. Existe uma maneira de aplicar o conceito de DragDropManager compartilhado acima? Tentei até agora e não tenho acesso a DragDropManager porque está em dnd-core

Obrigado pela sua ajuda e por esta biblioteca incrível!

PS Se for importante, estou usando o ngReact.

@ prakhar1989 @globexdesigns @gaearon

Estou me perguntando a mesma coisa por que o back-end HTML5 não pode simplesmente reutilizar o back-end se vários forem usados? De acordo com meu comentário anterior, isso está realmente tornando o reac-dnd inutilizável para mim, pois tenho várias áreas de reação em uma página angular que precisam ser capazes de DnD entre si e estou batendo em uma parede com isso.

Alguém tem alguma solução rápida para isso? Estou em um ponto morto no meu desenvolvimento.

@abobwhite Esta é a forma como eu tenho _solved_-lo. Definitivamente não é uma ótima solução, mas parece funcionar a partir de agora.

Espero que isto ajude,

Obrigado, @ prakhar1989 ! Mas não estou entendendo como a exportação múltipla com uma envolvida com contexto e outra não resolve o problema. Meu problema não é ser potencialmente incorporado em outro aplicativo com react-dnd, mas sim não ser capaz de envolver toda a minha área habilitada para dnd (com vários componentes / diretivas react e angulares) em react, então eu estava tentando envolver o contexto em torno de apenas esses componentes de reação na minha página que suportam DnD ... Eu adoraria tentar a abordagem de @gaearon acima, mas não tenho acesso ao DragDropManager para criar um novo ...

Estou tendo exatamente o mesmo problema. Eu concordo totalmente com @abobwhite que isso torna os componentes menos reutilizáveis.

Este tópico me levou a resolver meu problema invariável movendo HTML5Backend e DragDropContext cima em minha hierarquia de componentes, assim como os documentos recomendam .

Este é um problema estranho. Estou trabalhando em um componente reordenável aninhado e tenho DragDropContext aninhado dentro do pai. Parece funcionar de forma independente (mas ainda tem DragDropContext aninhado).

Mas quando uso esse componente dentro de outro projeto que tem DragDropContext inicializado acima da hierarquia, recebo este erro.

Encontrei esse problema com um aplicativo no qual estou trabalhando. Eu tinha controle total sobre todos os componentes, então acabei usando @DragDropContext(HTMLBackend) vez de usar algo muito próximo ao código @gaearon neste comentário para criar um decorator que daria um arrasto compartilhado largue o contexto. Funciona muito bem.

Eu simplesmente tive a mesma coisa acontecendo comigo e o problema era que eu tinha o ComponentA que usava @DragDropContext(HTMLBackend) e o ComponentB que também tinha @DragDropContext(HTMLBackend) .

ComponentB foi importado em ComponentA, o que causou o erro para mim. Tudo que eu tive que fazer foi remover o DragDropContext do ComponentB e funcionou.

E se houver 2 DrapDropContext no aplicativo, mas eles não forem pai e filho?

O meu caso:

  1. Inicialize o primeiro contexto dnd
  2. Inicialize como irmão de um segundo contexto dnd. 💣

Não consigo verificar childContext porque o segundo componente não é filho do primeiro componente / contexto dnd

Para resolver meu problema, fiz um singleton com este código:

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

E então, em todos os componentes que têm um filho que tinha DragDropContext(HTML5Backend) eu removo esse filho e em seus pais eu faço o seguinte:

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

const ParentComponent = React.createClass({

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

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

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

Acho que a chave é que eu só inicializo o contexto dnd uma vez. O que você acha?

@andresgutgon obrigado. Funciona pra mim também

@andresgutgon sua solução é ótima, mas é estranho que DrapDropContext não destrua DragDropManager em componentWillUnmount , ou seja, se você não usar 2 DrapDropContext s ao mesmo tempo, mas em 2 páginas diferentes - você ainda não pode usá-las. Vou tentar seu hack no meu caso, mas ainda é 100% estranho ter hacks para situações tão triviais.

Também tem alguma idéia de por que ter 2 DragDropManager é algo ruim / errado em react-dnd ?

Outra abordagem que é um pouco mais limpa é criar um módulo que gere o decorador para um determinado back-end e, em seguida, usar o decorador quando necessário:

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

Olá, alguém sabe como resolver quando você tem um componente empacotado em DragDropContext e outro componente que usa DragDropContext também, mas este componente (react big calendar) é um pacote npm, então remova-o de lá não é uma solução e eles estão próximos um do outro, não pai-filho ... você pode dar uma olhada aqui https://github.com/martinnov92/TSCalendar (é um trabalho em andamento - então está um pouco bagunçado: D) obrigado

Eu acredito que tenho exatamente o mesmo problema com o uso do gerenciador de janelas de

Portanto, estou supondo que isso não foi resolvido diretamente no react-dnd, mas existem algumas soluções alternativas que podemos aplicar.

existe um exemplo de um código de trabalho completo que corrige esse problema?

A solução de @gcorne funciona

Oi, eu tenho o mesmo problema, mas usamos react-data-grid e tentamos adicionar react-big-calendar
Após adicionar o componente que inclui react-big-calendar, temos o erro "Erro não detectado: não é possível ter dois back-ends HTML5 ao mesmo tempo."

react-big-calendar use react-dnd e react-dnd-html5-backend como resolver esse problema?

Olá @szopenkrk , você tentou https://github.com/react-dnd/react-dnd/issues/186#issuecomment -232382782?
Mas acho que você precisaria fazer uma solicitação pull para react-big-calendar para aceitar um back-end Dnd

Provavelmente um pouco tarde demais, mas descobri uma solução semelhante, mas ligeiramente diferente. Implementei um componente de ordem superior e simplesmente o uso em todos os meus componentes compatíveis com DragDropContext.

O código é semelhante a este (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);
}

E use-o da seguinte maneira em seus componentes:

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

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

export default withDragDropContext<IMyClassProps>(MyClass);

NB
Ainda não tentei, mas você provavelmente conseguirá preencher a variável de contexto durante a declaração:

const context = DragDropContext(HTML5Backend);

e pule a parte if (!context) {... .

@codeaid Obrigado!

Encontrei a mesma situação que @andresgutgon , mas não consegui resolver com o método dele e todo método aqui que tentei e ninguém funciona, alguém pode me ajudar? Obrigado.

Por que nenhuma das soluções aqui funcionou @ guang2013 ?

Não, ainda não há soluções, não sei, tentei arrastar o cartão para bigCalendar e fiz o cartão como dragSource e o cardsList como DragAndDropContext, o calendário como outro DragAndDropContext, então dois erros de html5backend descartados, tentei usar qualquer método fornecido aqui, mas ninguém pode resolver meu problema. @andresgutgon , quando você fica online, posso falar diretamente com você sobre isso? Muito apreciado.

@ guang2013 se você estiver usando uma biblioteca que depende de DragDropContext do react-dnd, esta técnica não funcionará porque a biblioteca não usará seu dndContext unificado. Algumas bibliotecas, como react-sortable-tree, permitirão que você use os componentes sem um contexto, para que você mesmo possa agrupá-los.

A solução de @gcorne funcionou para mim para permitir o uso do react-dnd em um aplicativo com react-hot-loader. Ainda assim, estou um pouco surpreso que não funcionou fora da caixa!

Problema antigo, mas caso outra pessoa do Google acabe aqui:

Eu tinha apenas 1 provedor de DND na minha página, não estava integrando nenhuma outra biblioteca que possuísse DND, mas mesmo assim acabei dando de cara com esse erro, de forma um tanto aleatória.

Meu problema era que o DragDropContextProvider estava dentro do elemento BrowserRouter do ReactRouter, o que fazia o HTML5Backend ser reconstruído em cada navegação, e se tanto a página antiga (que foi navegada para fora) quanto a nova (aquela para a qual navegou) tinham elementos DND, o erro acima ocorreria.

A solução foi mover o DragDropContextProvider para fora do BrowserRouter.

Este é para aqueles mortais que experimentaram o ReactDnD pela primeira vez, seguiram o tutorial e acabaram com um tabuleiro de xadrez de 64 quadrados.
Eu poderia arrastar meu cavaleiro como quisesse.
O problema foi quando tentei soltá-lo em um dos BoardSquare , ele vomitou com o problema de back-end do HTML5 2.

O conserto

Como outros mencionaram em comentários antes, mova a renderização DragDropContextProvider para fora do ciclo de renderização do aplicativo.
Como em, não faça um ReactDOM.render diretamente como um retorno de chamada para a função observe .
Em vez disso, faça o seguinte:

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;

Eu acho que isso está relacionado a esse problema
https://github.com/prakhar1989/react-tags/issues/497

@gaearon Eu sei que isso é antigo, mas como faço para realmente obter DragDropManager em seu exemplo? Não é exportado para lugar nenhum.

defaultManager = new DragDropManager(HTML5Backend);

Meu problema é que estou renderizando alguns widgets em uma página por meio de alguma API de terceiros e não consigo mover DragDropContextProvider acima do meu widget real.

Resolvido esse problema, excluindo a compilação antiga.
apenas exclua a pasta dist ou edite index.html. Não sei o problema exato, mas funcionou para mim

Felizmente, consegui encontrar a resposta (oculta) de @gcorne (https://github.com/react-dnd/react-dnd/issues/186#issuecomment-282789420). Ele resolveu meu problema - que se supôs ser complicado - instantaneamente.
@ prakhar1989 Tenho a sensação de que é uma resposta de parte real e que deveria ser destacada de alguma forma, então, talvez um link na descrição do bug?

A solução que descobri que funciona para mim e me permite usar o back-end HTML5 ou Touch é:

Crie um componente HOC singleton:

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),
}

Em seguida, um componente de provedor:

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

E use <DragDrop isTouch={props.isTouch} /> sempre que eu precisar.

Para desenvolvedores que enfrentam o mesmo problema, você pode dar uma olhada nesta solução HOC

Estou tendo esse problema agora com os testes de Jest. HTML5-Backend é tratado como um singleton nos testes de Jest (é por isso que o problema está acontecendo ... eu acho)

Problema detalhado no SO:

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

Usando ganchos

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

const manager = useRef(createDndContext(HTML5Backend));

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

Uma solução melhor usando Hooks (obrigado @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>;
}

então você pode usar em outro lugar:

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

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

Minha solução:
Faça com que o componente filho não importe react-dnd diretamente.
Passe o DragDropContext e HTML5Backend do componente pai para filho.

Adicionar uma chave exclusiva à tag DragDropContextProvider resolve o problema <DragDropContextProvider backend={HTML5Backend} key={Math. random()}></DragDropContextProvider>

No texto datilografado, fiz o componente abaixo. (obrigado @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;

E usei um componente como este

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

Adicionar uma chave exclusiva à tag DragDropContextProvider resolve o problema <DragDropContextProvider backend={HTML5Backend} key={Math. random()}></DragDropContextProvider>

bom trabalho!

Esta página foi útil?
0 / 5 - 0 avaliações