React-dnd: No se pueden tener dos backends HTML5 al mismo tiempo

Creado en 8 jun. 2015  ·  62Comentarios  ·  Fuente: react-dnd/react-dnd

Hola dan

Solo uno rápido: estoy tratando de usar mi propio componente que tiene react-dnd como dependencia en otra aplicación que _ él mismo usa_ react-dnd por lo que se esperaba el error anterior. En este caso, ¿cuál sería la mejor manera de solucionar este problema?

Dado que el otro componente es el mío, puedo eliminar la llamada DragDropContext mientras exporto el componente, pero eso sacrifica la capacidad de reutilización del componente. ¿Qué recomiendas?

Comentario más útil

Otro enfoque que es un poco más limpio es crear un módulo que genere el decorador para un backend en particular y luego usar el decorador donde sea necesario:

lib / withDragDropContext.js

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

export default DragDropContext(HTML5Backend);

componentes / 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 comentarios

Consulte la fuente de DragDropContext . Creo que debería poder reutilizar el administrador existente si lo especifica un componente sobre el árbol. Pero sigue siendo una pregunta delicada ... ¿Tiene alguna solución propuesta?

Creo que debería poder reutilizar el administrador existente si lo especifica un componente sobre el árbol.

Incluso si esto es posible, el componente que se exporta también debería poder funcionar de forma independiente, y en los casos en que exista el backend (en la aplicación donde se está utilizando el componente) en algún lugar de la cadena, debería reutilizarse. Trataré de leer la fuente y veré si se puede lograr.

Desafortunadamente, la única solución en la que puedo pensar en este momento es exportar el componente final tal como está y esperar que el usuario agregue un DragDropContext con un backend de su elección. ¿Qué piensas?

el componente que se exporta también debería poder funcionar de forma independiente, y en los casos en que exista el backend (en la aplicación donde se está utilizando el componente) en algún lugar de la cadena debería reutilizarse.

Sí, esto es posible si no se usa DragDropContext completo y, en su lugar, se usa manualmente dragDropManager en el contexto. Su componente puede mirar en contexto y pasar el dragDropManager en el contexto o crear el suyo propio. Aunque parece algo frágil.

Desafortunadamente, la única solución en la que puedo pensar en este momento es exportar el componente final tal como está y esperar que el usuario agregue un DragDropContext con un backend de su elección. ¿Qué piensas?

Creo que esta es la solución más flexible. También puede tener opiniones allí y exportar <MyComponentContext> que aplica DragDropContext(HTML5Backend) .

exportar <MyComponentContext> que se aplica DragDropContext(HTML5Backend)

Lo siento, no entiendo bien esto. ¿Puedes aclarar esto un poco más?

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

y puede decirle a los usuarios que envuelvan su componente de nivel superior en MyTagControlContext o que usen DragDropContext directamente si _ ya_ usan React DnD.

¡Ah! ¿Qué tal esto? ¿Esto se ve demasiado feo?

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

El uso puede 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.

No creo que esto funcione porque cada <ReactTags> obtendría su propia copia del backend y provocaría el error invariante que pegó anteriormente porque esos backends manejan los mismos eventos de ventana global.

Lo que creo que funcionará es que puede crear manualmente dragDropManager (al igual que DragDropContext hace internamente) y usar la misma instancia para todas las instancias ReactTag , con un respaldo al gerente definido en context .

Me refiero a exportar algo como esto desde tu 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} />
    }
}

¡Muchas gracias, Dan! Probaré esto y me pondré en contacto contigo. Gracias por compartir el código: sonriendo:

No hay problema. Si lo hace así, simplemente exporte esa clase en lugar de exportar ReactTags directamente. Debe ser utilizable "tal cual", sin envoltorios ni decoradores.

¡Entonces Dan! Por el gusto de hacerlo, estaba probando la solución de exportación múltiple anterior:

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

En mi otra aplicación, intenté importar el componente sin el contexto y, para mi deleite, parece que funciona bien.

¿Crees que esta es una solución hacky y debería seguir adelante con lo que has propuesto o debería dejar que esto sea así?

@ prakhar1989 ¿Estás seguro de que esto funciona con varios <Tags /> en la página?

¡Me tuviste por un segundo allí! : Lengua_salida_atascada:

img

¡Afortunadamente funciona!

Hmm, entonces tal vez esté bien ;-). ¡Avíseme si tiene algún problema con este enfoque!

¡Lo haré! Muchas gracias de nuevo por toda su ayuda.

PD: ¿Tiene mejores ideas de nombres para WithContext y WithoutContext ?

@ prakhar1989 Probablemente solo exportaría la versión WithContext directamente y pondría NoContext como un campo estático en ella.

Me estoy encontrando con un problema similar y me gustaría entender mejor por qué existe esta limitación en primer lugar porque hace que escribir componentes reutilizables sea bastante difícil. Tal como están las cosas, cada componente que usa react-dnd necesita ser consciente de varios contextos que pueden existir en la aplicación y tratarlos en consecuencia. Sería preferible si cada componente pudiera gestionar su propio comportamiento / contexto de arrastre independientemente de lo que pueda estar sucediendo en el resto de la aplicación.

Por ejemplo, es posible que desee tener una pantalla de aplicación que tenga varios componentes de carga de archivos, un menú ordenable y un juego con elementos arrastrables. Cada uno de esos componentes tiene formas muy diferentes de lidiar con los eventos de arrastre y realmente debería estar a cargo de su propio contexto.

Mi primera pregunta es ¿por qué no hacer esto simplemente dentro del 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 qué no hacer esto simplemente dentro del código HTML5Backend?

Es un excelente punto. Si el componente es capaz de detectar múltiples backends, ¿no puede tener directamente la lógica de recurrir al backend existente en su alcance?

Hola @gaearon : también me encuentro con este problema, excepto en mi caso, tengo una página en la que he reunido componentes de reacción dispares dentro de una plantilla angular debido al rendimiento (angular). Lo que tengo es una página que crea preguntas, opciones y más en una estructura de árbol recursiva. También tengo una barra de herramientas y una biblioteca de preguntas que usan DnD para agregar cosas al árbol de preguntas. Mi problema es que ahora he configurado varios componentes de reacción que viven dentro de un contexto angular. Debido a esto, estoy envolviendo cada uno de ellos con un DragDropContext que causa este error. Intenté seguir el hilo anterior, pero no me queda del todo claro qué podría hacer para que estos componentes de reacción separados compartan un contexto sin convertir todo lo demás en mi página para que sea React (no ideal). Estoy un poco familiarizado con la sintaxis de ES6 pero estoy trabajando en un proyecto que todavía usa ES5. ¿Hay alguna forma de aplicar el concepto compartido DragDropManager de arriba? Lo he probado hasta ahora y parece que no tengo acceso a DragDropManager ya que está en dnd-core

¡Gracias por tu ayuda y por esta increíble biblioteca!

PD: Si importa, estoy usando ngReact.

@ prakhar1989 @globexdesigns @gaearon

Me pregunto lo mismo por qué el backend HTML5 no puede simplemente reutilizar el backend si se usan varios. Según mi comentario anterior, esto realmente hace que react-dnd sea inutilizable para mí, ya que tengo múltiples áreas de reacción dentro de una página angular que necesitan poder DnD entre sí y estoy golpeando una pared con esto.

¿Alguien tiene alguna solución rápida para esto? Estoy en un punto muerto en mi desarrollo.

@abobwhite Así es como lo he _resuelto_. Definitivamente no es una gran solución, pero parece funcionar a partir de ahora.

Espero que esto ayude,

¡Gracias, @ prakhar1989 ! Pero no estoy siguiendo cómo la exportación múltiple con una envuelta con contexto y otra no resuelve el problema. Mi problema no es que esté potencialmente incrustado en otra aplicación con react-dnd, sino que no puedo envolver toda mi área habilitada para dnd (con varios componentes / directivas react y angulares) en reaccionar, así que estaba tratando de envolver el contexto solo en esos componentes de reacción en mi página que admiten DnD ... Me encantaría probar el enfoque de @gaearon desde arriba, pero no tengo acceso a DragDropManager para crear uno nuevo ...

Tengo exactamente el mismo problema. Estoy totalmente de acuerdo con @abobwhite en que esto hace que los componentes sean menos reutilizables.

Este hilo me llevó a resolver mi problema invariante moviendo HTML5Backend y DragDropContext más arriba en mi jerarquía de componentes, tal como recomiendan los documentos .

Este es un problema extraño. Estoy trabajando en un componente reordenable anidado y tengo DragDropContext anidado dentro del principal. Parece funcionar de forma independiente (pero todavía tiene DragDropContext anidado).

Pero cuando uso ese componente dentro de otro proyecto que tiene DragDropContext inicializado por encima de la jerarquía, aparece este error.

Me encontré con este problema con una aplicación en la que estoy trabajando. Tenía control total sobre todos los componentes, así que terminé en lugar de usar @DragDropContext(HTMLBackend) Terminé usando algo muy parecido al código este comentario para crear un decorator que daría un arrastre compartido quitar contexto. Funciona muy bien.

Me pasó lo mismo y el problema fue que tenía ComponentA que usaba @DragDropContext(HTMLBackend) y luego ComponentB que también tenía @DragDropContext(HTMLBackend) .

ComponentB se importó en ComponentA, lo que me provocó el error. Todo lo que tenía que hacer era eliminar DragDropContext de ComponentB y funcionó.

¿Qué pasa si hay 2 DrapDropContext en la aplicación pero no son padre e hijo?

Mi caso:

  1. Inicializar el primer contexto y
  2. Inicialice como sibbling un segundo contexto dnd. 💣

No puedo verificar childContext porque el segundo componente no es un niño del primer componente / contexto dnd

Para resolver mi problema hice un singleton con 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;
}

Y luego en todos los componentes que tienen un hijo que tenía DragDropContext(HTML5Backend) lo quito a esos hijos y en sus padres hago esto:

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

const ParentComponent = React.createClass({

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

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

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

Creo que la clave es que solo inicializo el contexto dnd una vez. ¿Qué piensas?

@andresgutgon gracias. Funciona para mi tambien

@andresgutgon tu solución es genial, pero es extraño que DrapDropContext no destruya DragDropManager en componentWillUnmount , es decir, si no usas 2 DrapDropContext s al mismo tiempo, pero en 2 páginas diferentes, todavía no puede usarlas. Voy a probar tu truco en mi caso, pero sigue siendo 100% extraño incluso tener trucos para una situación tan trivial.

¿También alguna idea de por qué tener 2 DragDropManager es algo malo / incorrecto en react-dnd ?

Otro enfoque que es un poco más limpio es crear un módulo que genere el decorador para un backend en particular y luego usar el decorador donde sea necesario:

lib / withDragDropContext.js

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

export default DragDropContext(HTML5Backend);

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

Hola, ¿alguien sabe cómo resolver cuando tiene un componente envuelto en DragDropContext y luego otro componente que usa DragDropContext también, pero este componente (reaccionar calendario grande) es un paquete npm, así que elimínelo de allí no hay una solución y ellos están uno al lado del otro, no entre padres e hijos ... puedes echar un vistazo aquí https://github.com/martinnov92/TSCalendar (es un trabajo en progreso, por lo que es un poco complicado: D) gracias

Creo que tengo exactamente el mismo problema con el uso del administrador de ventanas de

Así que supongo que esto no se resuelve directamente en react-dnd, pero hay algunas soluciones que podemos aplicar.

¿Existe un ejemplo de un código de trabajo completo que solucione este problema?

La solución de @gcorne funciona

Hola, tengo el mismo problema, pero usamos react-data-grid e intentamos agregar react-big-calendar
Después de agregar el componente que incluye react-big-calendar, tenemos el error "Error no detectado: no se pueden tener dos backends HTML5 al mismo tiempo".

react-big-calendar use react-dnd y react-dnd-html5-backend ¿cómo resolver ese problema?

Hola @szopenkrk, ¿ https://github.com/react-dnd/react-dnd/issues/186#issuecomment -232382782?
Pero creo que necesitaría hacer una solicitud de extracción para reaccionar-big-calendar para aceptar un backend Dnd

Probablemente un poco tarde, pero se me ocurrió una solución similar pero ligeramente diferente. Implementé un componente de orden superior y simplemente lo uso en todos mis componentes compatibles con DragDropContext.

El código tiene este aspecto (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);
}

Y luego úselo de la siguiente manera en sus componentes:

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

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

export default withDragDropContext<IMyClassProps>(MyClass);

nótese bien
Todavía no lo he intentado, pero probablemente debería poder completar la variable de contexto durante la declaración:

const context = DragDropContext(HTML5Backend);

y luego omita la parte if (!context) {... .

@codeaid ¡ Gracias!

Me encontré con la misma situación que @andresgutgon , pero no pude resolver con su método y todo el método aquí lo probé y nadie funciona, ¿hay alguien que pueda ayudarme? Gracias.

¿Por qué no funcionó @ guang2013 ninguna de las soluciones aquí?

No, todavía no hay soluciones, no lo sé, intenté arrastrar la tarjeta a bigCalendar, e hice la tarjeta como dragSource y la cardsList como DragAndDropContext, el calendario como otro DragAndDropContext, luego arrojaron dos errores html5backend, lo intenté utilizar cualquier método proporcionado aquí, pero nadie puede resolver mi problema. @andresgutgon , ¿cuándo estás en línea, puedo hablar contigo directamente sobre esto? Muy apreciado.

@ guang2013 si está usando una biblioteca que depende de DragDropContext de react-dnd, esta técnica no funcionará porque la biblioteca no usará su dndContext unificado. Algunas bibliotecas como react-sortable-tree te permitirán usar los componentes sin un contexto para que puedas ajustarlos tú mismo.

La solución de @gcorne me funcionó para permitir el uso de react-dnd en una aplicación con react-hot-loader. Aún así, estoy un poco sorprendido de que no haya funcionado de inmediato.

Problema antiguo, pero en caso de que alguien más termine aquí de Google:

Solo tenía 1 proveedor de DND en mi página, no estaba integrando ninguna otra biblioteca que tenga DND, pero aún así terminé encontrándome con este error, algo aleatorio.

Mi problema fue que DragDropContextProvider estaba dentro del elemento BrowserRouter de ReactRouter, lo que hizo que HTML5Backend se reconstruyera en cada navegación, y si tanto la página anterior (de la que se navegó) como la nueva (la que se navegó) tenían elementos DND, se produciría el error anterior.

La solución fue sacar DragDropContextProvider de BrowserRouter.

Este es para aquellos mortales que probaron ReactDnD por primera vez, siguieron el tutorial y terminaron con un tablero de ajedrez de 64 cuadrados.
Podría arrastrar a mi caballero como quisiera.
El problema fue que cuando traté de colocarlo en uno de los BoardSquare , arrojó el problema del backend de 2 HTML5.

La solución

Como otros han mencionado anteriormente en los comentarios, mueva el renderizado DragDropContextProvider fuera del ciclo de renderizado de la aplicación.
Como en, no haga directamente un ReactDOM.render como devolución de llamada a la función observe .
En su lugar, haz esto:

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;

Creo que esto está relacionado con este problema.
https://github.com/prakhar1989/react-tags/issues/497

@gaearon Sé que esto es antiguo, pero ¿cómo consigo realmente DragDropManager en tu ejemplo? No se exporta a ninguna parte.

defaultManager = new DragDropManager(HTML5Backend);

Mi problema es que estoy renderizando algunos widgets en una página a través de una API de terceros y no puedo mover DragDropContextProvider más alto que mi widget real.

Resolvió este problema eliminando la versión anterior.
simplemente elimine la carpeta dist o edite index.html. No sé el problema exacto, pero esto funcionó para mí.

Afortunadamente, pude encontrar la respuesta (oculta) de @gcorne (https://github.com/react-dnd/react-dnd/issues/186#issuecomment-282789420). Resolvió mi problema, que se suponía que era complicado, instantáneamente.
@ prakhar1989 Tengo la sensación de que es una respuesta de fiesta real y que debería resaltarse de alguna manera, así que ¿tal vez vincularlo en la descripción del error?

La solución que descubrí que funciona para mí y me permite usar HTML5 o Touch backend es:

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

Luego, un componente de proveedor:

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

Y use <DragDrop isTouch={props.isTouch} /> donde lo necesite.

Para los desarrolladores que se encuentran con el mismo problema, pueden echar un vistazo a esta solución HOC

Me estoy encontrando con este problema ahora con las pruebas de Jest. HTML5-Backend se trata como un singleton en las pruebas de Jest (por eso está sucediendo el problema ... creo)

Problema detallado en 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>
)

Una mejor solución usando Hooks (gracias @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>;
}

entonces puedes usar en otro lugar:

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

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

Mi solución:
Haga que el componente secundario no importe react-dnd directamente.
Pase el componente DragDropContext y HTML5Backend del componente principal al secundario.

Agregar una clave única a la etiqueta DragDropContextProvider resuelve el problema <DragDropContextProvider backend={HTML5Backend} key={Math. random()}></DragDropContextProvider>

En el texto mecanografiado, hice el siguiente componente. (gracias @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;

Y usé un componente como este

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

Agregar una clave única a la etiqueta DragDropContextProvider resuelve el problema <DragDropContextProvider backend={HTML5Backend} key={Math. random()}></DragDropContextProvider>

¡buen trabajo!

¿Fue útil esta página
0 / 5 - 0 calificaciones