React-dnd: Нельзя одновременно использовать два серверных модуля HTML5

Созданный на 8 июн. 2015  ·  62Комментарии  ·  Источник: react-dnd/react-dnd

Привет Дэн,

Просто быстрый - я пытаюсь использовать свой собственный компонент, который имеет react-dnd в качестве зависимости в другом приложении, которое сам использует_ react-dnd поэтому ошибка выше ожидалась. В таком случае, как лучше всего это исправить?

Поскольку другой компонент является моим собственным, я могу удалить вызов DragDropContext при экспорте компонента, но тогда это принесет в жертву возможность повторного использования компонента. Что посоветуете?

Самый полезный комментарий

Другой подход, который немного чище, - создать модуль, который генерирует декоратор для определенного бэкенда, а затем использовать декоратор там, где это необходимо:

lib / withDragDropContext.js

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

export default DragDropContext(HTML5Backend);

компоненты / 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);

Все 62 Комментарий

Ознакомьтесь с исходным кодом DragDropContext . Я думаю, вы сможете повторно использовать существующий менеджер, если он указан в компоненте над деревом. Но это пока непростой вопрос .. Есть ли у вас какие-нибудь решения?

Я думаю, вы сможете повторно использовать существующий менеджер, если он указан в компоненте над деревом.

Даже если это возможно, экспортируемый компонент также должен иметь возможность работать независимо, а в случаях, когда серверная часть существует (в приложении, где используется компонент), где-то вверху цепочки следует использовать повторно. Я постараюсь прочитать исходник и посмотреть, можно ли это осуществить.

К сожалению, единственное решение, которое я могу придумать прямо сейчас, - это экспортировать последний компонент как есть и ожидать, что пользователь добавит DragDropContext с выбранным им сервером. Что вы думаете?

экспортируемый компонент также должен иметь возможность работать независимо, а в тех случаях, когда серверная часть существует (в приложении, в котором используется компонент), где-то вверху цепочки следует использовать повторно.

Да, это возможно, если совсем не использовать DragDropContext , а вместо этого вручную использовать dragDropManager в контексте. Ваш компонент может изучить контекст и либо передать существующий dragDropManager вниз по контексту, либо создать свой собственный. Хотя это кажется несколько хрупким.

К сожалению, единственное решение, которое я могу сейчас придумать, - это экспортировать последний компонент как есть и ожидать, что пользователь добавит DragDropContext с выбранным им сервером. Что вы думаете?

Думаю, это наиболее гибкое решение. Вы также можете проявить самоуверенность и экспортировать <MyComponentContext> который применяет DragDropContext(HTML5Backend) .

экспорт <MyComponentContext> который применяет DragDropContext(HTML5Backend)

Простите, я не совсем понимаю этого. Не могли бы вы прояснить это немного подробнее?

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

и вы можете сказать пользователям, что они должны либо обернуть свой компонент верхнего уровня в MyTagControlContext либо использовать DragDropContext напрямую, если они _ уже_ используют React DnD.

Ах! Как насчет этого? Это выглядит слишком некрасиво?

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

Тогда использование может быть чем-то вроде

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.

Я не думаю, что это сработает, потому что каждый <ReactTags> получит свою собственную копию бэкэнда и приведет к инвариантной ошибке, которую вы вставили выше, потому что эти бэкэнды обрабатывают одни и те же глобальные события окна.

Думаю, сработает то, что вы можете вручную создать dragDropManager (точно так же, как DragDropContext делает внутри) и использовать один и тот же экземпляр для всех экземпляров ReactTag - с откатом к менеджер, определенный в context .

Я имею в виду экспорт чего-то вроде этого из вашей библиотеки:

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

Большое спасибо, Дэн! Я попробую и вернусь к вам. Спасибо, что поделились кодом: grinning:

Нет проблем .. Если вы сделаете это так, просто экспортируйте этот класс вместо прямого экспорта ReactTags . Его следует использовать «как есть», без каких-либо оболочек или декораторов.

Итак, Дэн! Черт возьми, я пробовал решение для множественного экспорта выше -

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

В другом моем приложении я попытался импортировать компонент без контекста, и , к моему большому удовольствию, он, похоже, работает нормально!

Как вы думаете, это хакерское решение, и мне следует продолжить то, что вы предложили, или я должен позволить этому быть?

@ prakhar1989 Вы уверены, что это работает с несколькими <Tags /> на странице?

Ты был там на секунду! : stuck_out_tongue:

img

К счастью, это работает!

Хм, тогда может и нормально ;-). Сообщите мне, если у вас возникнут проблемы с этим подходом!

Сделаю! Еще раз спасибо за вашу помощь.

PS: Есть ли у вас лучшие идеи именования для WithContext и WithoutContext ?

@ prakhar1989 Я бы, вероятно, просто экспортировал WithContext версию напрямую и поместил бы NoContext в качестве статического поля.

Я сталкиваюсь с аналогичной проблемой, и я хотел бы лучше понять, почему существует это ограничение, в первую очередь, потому что оно затрудняет написание повторно используемых компонентов. Как бы то ни было, каждый компонент, использующий response-dnd, должен знать о различных контекстах, которые могут существовать в приложении, и соответственно обрабатывать их. Было бы предпочтительнее, если бы каждый компонент мог управлять своим собственным поведением / контекстом перетаскивания независимо от того, что еще может происходить в остальной части приложения.

Например, мне может потребоваться экран приложения, в котором есть несколько компонентов загрузки файлов, сортируемое меню и игра с перетаскиваемыми элементами. Каждый из этих компонентов имеет разные способы обработки событий перетаскивания и действительно должен отвечать за свой собственный контекст.

Мой первый вопрос: почему бы просто не сделать это внутри кода 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;
    ...
  }

почему бы просто не сделать это внутри кода HTML5Backend

Это отличный момент. Если компонент может обнаруживать несколько бэкэндов, не может ли он напрямую иметь логику возврата к существующему бэкэнду в области видимости?

Привет @gaearon - я тоже DragDropContext что вызывает эту ошибку. Я пробовал следовать приведенному выше потоку, но мне не совсем понятно, что я могу сделать, чтобы эти отдельные компоненты реагирования разделяли контекст без преобразования всего остального на моей странице в React (не идеально). Я немного знаком с синтаксисом ES6, но работаю над проектом, который все еще использует ES5. Есть ли способ применить общую концепцию DragDropManager сверху? Я пробовал это до сих пор, и у меня нет доступа к DragDropManager так как он находится в dnd-core

Спасибо за вашу помощь и за эту потрясающую библиотеку!

PS Если важно, я использую ngReact.

@ prakhar1989 @globexdesigns @gaearon

Мне интересно то же самое, почему бэкэнд HTML5 не может просто повторно использовать бэкэнд, если используются несколько? Согласно моему предыдущему комментарию, это действительно делает react-dnd непригодным для меня, поскольку у меня есть несколько областей реакции на угловой странице, которые должны иметь возможность DnD между собой, и я наталкиваюсь на стену с этим.

У кого-нибудь есть быстрое решение для этого? Я на мертвой точке в своем развитии.

@abobwhite Вот как я это решил. Это определенно не лучшее решение, но, похоже, на данный момент оно работает.

Надеюсь это поможет,

Спасибо, @ prakhar1989 ! Но я не слежу за тем, как множественный экспорт с одним, обернутым контекстом, а другой не решает проблему. Моя проблема не может быть потенциально встроена в другое приложение с помощью response-dnd, а скорее в том, что я не могу обернуть всю мою область с поддержкой dnd (с несколькими реагирующими и угловыми компонентами / директивами) в реакцию, поэтому я пытался обернуть контекст только вокруг этих реагировать на компоненты на моей странице, которые поддерживают DnD ... Я бы хотел попробовать подход @gaearon сверху, но у меня нет доступа к DragDropManager , чтобы создать новый ...

У меня точно такая же проблема. Я полностью согласен с @abobwhite в том, что это делает компоненты менее

Этот поток привел меня к решению моей проблемы с инвариантами, переместив HTML5Backend и DragDropContext выше в иерархии моих компонентов, как это рекомендуют документы .

Это странная проблема. Я работаю над вложенным переупорядочиваемым компонентом, и у меня есть DragDropContext, вложенный внутри родительского. Кажется, что он работает автономно (но все еще имеет вложенный DragDropContext).

Но когда я использую этот компонент внутри другого проекта, у которого DragDropContext инициализирован над иерархией, я получаю эту ошибку.

Я столкнулся с этой проблемой с приложением, над которым работаю. У меня был полный контроль над всеми компонентами, поэтому вместо использования @DragDropContext(HTMLBackend) я использовал что-то очень похожее на код этом комментарии, чтобы создать decorator , который давал бы общее перетаскивание отбросить контекст. Это действительно хорошо работает.

Со мной случилось то же самое, и проблема заключалась в том, что у меня был ComponentA, который использовал @DragDropContext(HTMLBackend) а затем ComponentB, который также имел @DragDropContext(HTMLBackend) .

ComponentB был импортирован в ComponentA, что вызвало у меня ошибку. Все, что мне нужно было сделать, это удалить DragDropContext из ComponentB, и это сработало.

Что делать, если в приложении есть 2 DrapDropContext , но они не являются родительскими и дочерними?

Мое дело:

  1. Инициализировать первый контекст dnd
  2. Инициализировать второй контекст dnd как родственный. 💣

Я не могу проверить childContext, потому что второй компонент не является потомком первого компонента / контекста dnd

Чтобы решить мою проблему, я сделал синглтон с этим кодом:

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

И затем во всех компонентах, у которых есть дочерний элемент с DragDropContext(HTML5Backend) я удаляю его из этого дочернего элемента, а в их родителях я делаю следующее:

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

const ParentComponent = React.createClass({

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

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

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

Я думаю, что дело в том, что я инициализирую dnd context только один раз. Что вы думаете?

@andresgutgon, спасибо. У меня тоже работает

@andresgutgon у вас DrapDropContext не уничтожает DragDropManager на componentWillUnmount , т.е. если вы не используете 2 DrapDropContext s одновременно, но на 2 разных страницах - вы все равно не можете их использовать. Я собираюсь попробовать ваш хакер в моем случае, но все еще на 100% странно иметь хаки для такой тривиальной ситуации.

Также есть идеи, почему наличие 2 DragDropManager - это что-то плохое / неправильное в react-dnd ?

Другой подход, который немного чище, - создать модуль, который генерирует декоратор для определенного бэкенда, а затем использовать декоратор там, где это необходимо:

lib / withDragDropContext.js

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

export default DragDropContext(HTML5Backend);

компоненты / 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);

Привет, кто-нибудь знает, как решить, когда у вас есть один компонент, завернутый в DragDropContext, а затем другой компонент, который также использует DragDropContext, но этот компонент (реагировать на большой календарь) является пакетом npm, поэтому удалить его оттуда не является решением, и они находятся рядом друг с другом, а не родитель-ребенок ... вы можете посмотреть здесь https://github.com/martinnov92/TSCalendar (он в стадии разработки - так что это немного беспорядочно: D) спасибо

Я считаю, что у меня точно такая же проблема с использованием оконного менеджера мозаики реакции, который использует response-dnd и мои собственные компоненты, которые также используют response-dnd.

Я предполагаю, что это не решено напрямую в react-dnd, но есть некоторые обходные пути, которые мы можем применить.

есть ли пример полного рабочего кода, исправляющего эту проблему?

Решение @gcorne работает

Привет, у меня такая же проблема, но мы используем react-data-grid и пытаемся добавить react-big-calendar
После добавления компонента, который включает в себя response-big-calendar, у нас появляется ошибка «Неперехваченная ошибка: невозможно иметь два бэкэнда HTML5 одновременно».

React-big-calendar использовать react-dnd и react-dnd-html5-backend как решить эту проблему?

Привет, @szopenkrk , вы пробовали это https://github.com/react-dnd/react-dnd/issues/186#issuecomment -232382782?
Но я думаю, вам нужно будет сделать запрос на перенос, чтобы response-big-calendar принять бэкэнд Dnd.

Возможно, слишком поздно, но я придумал похожее, но немного другое решение. Я реализовал компонент более высокого порядка и просто использую его во всех компонентах, поддерживающих DragDropContext.

Код выглядит так (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);
}

А затем используйте его в своих компонентах следующим образом:

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

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

export default withDragDropContext<IMyClassProps>(MyClass);

NB
Я еще не пробовал, но вы, вероятно, сможете заполнить переменную контекста во время объявления:

const context = DragDropContext(HTML5Backend);

а затем пропустите часть if (!context) {... .

@codeaid Спасибо!

Я встретил ту же ситуацию, что и @andresgutgon , но не смог решить с помощью его метода и всего метода, который я пробовал, и никто не работает, может ли кто-нибудь мне помочь? Спасибо.

Почему @ guang2013 здесь не сработало

Нет, все еще нет решений, я не знаю, я попытался перетащить карту в bigCalendar, и я сделал карту как dragSource и cardList как DragAndDropContext, календарь как другой DragAndDropContext, затем выскочили две ошибки html5backend, я попробовал использовать любой из представленных здесь методов, но никто не может решить мою проблему. @andresgutgon , когда ты в сети, могу я поговорить с тобой об этом напрямую? Очень признателен.

@ guang2013, если вы используете библиотеку, которая зависит от DragDropContext из response-dnd, этот метод не будет работать, потому что библиотека не будет использовать ваш унифицированный dndContext. Некоторые библиотеки, такие как react-sortable-tree, позволят вам использовать компоненты без контекста, поэтому вы можете обернуть их самостоятельно.

Решение @gcorne помогло мне использовать response-dnd в приложении с react-hot-loader. Тем не менее, я немного удивлен, что это не сработало из коробки!

Старая проблема, но на случай, если сюда попадет кто-то еще из Google:

На моей странице был только 1 провайдер DND, я не интегрировал никакую другую библиотеку с DND, но я все равно столкнулся с этой ошибкой, несколько случайно.

Моя проблема заключалась в том, что DragDropContextProvider находился внутри элемента BrowserRouter ReactRouter, который заставлял HTML5Backend реконструировать при каждой навигации, и если и старая страница (с которой был выполнен переход), и новая (та, на которую был выполнен переход) имели элементы DND, может произойти указанная выше ошибка.

Решением было переместить DragDropContextProvider из BrowserRouter.

Это для тех смертных, которые впервые попробовали ReactDnD, следовали инструкциям и в итоге получили шахматную доску на 64 квадрата.
Я мог волочить своего рыцаря, как мне было угодно.
Проблема заключалась в том, что когда я пытался бросить его в один из BoardSquare , возникла проблема с бэкэндом 2 HTML5.

Исправление

Как уже упоминалось ранее в комментариях, переместите рендеринг DragDropContextProvider за пределы цикла повторного рендеринга приложения.
Например, не выполняйте ReactDOM.render напрямую в качестве обратного вызова функции observe .
Вместо этого сделайте это:

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;

Я думаю это связано с этой проблемой
https://github.com/prakhar1989/react-tags/issues/497

@gaearon Я знаю, что это DragDropManager в вашем примере? Никуда не экспортируется.

defaultManager = new DragDropManager(HTML5Backend);

Моя проблема в том, что я визуализирую несколько виджетов на странице через какой-то сторонний API, и я не могу переместить DragDropContextProvider выше, чем мой настоящий виджет.

Решил эту проблему, удалив старую сборку.
просто удалите папку dist или отредактируйте index.html. Я не знаю точной проблемы, но это сработало для меня

К счастью, я смог найти (скрытый) ответ @gcorne (https://github.com/react-dnd/react-dnd/issues/186#issuecomment-282789420). Это решило мою проблему - которая, как предполагалось, была сложной - мгновенно.
@ prakhar1989 У меня такое чувство, что это настоящий партийный ответ и что его нужно как-то выделить, так что, может быть, свяжите его в описании ошибки?

Решение, которое я выяснил, которое работает для меня и позволяет мне использовать бэкэнд HTML5 или Touch:

Создайте одноэлементный компонент HOC:

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

Затем компонент поставщика:

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

И используйте <DragDrop isTouch={props.isTouch} /> везде, где мне это нужно.

Для разработчиков, которые сталкиваются с той же проблемой, вы можете взглянуть на это решение HOC.

Я столкнулся с этой проблемой сейчас с тестами Jest. HTML5-Backend рассматривается как синглтон в тестах Jest (вот почему проблема возникает ... я думаю)

Подробная проблема в SO:

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

Использование крючков

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

const manager = useRef(createDndContext(HTML5Backend));

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

Лучшее решение с использованием хуков (спасибо @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>;
}

так что вы можете использовать в другом месте:

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

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

Мое решение:
Сделайте так, чтобы дочерний компонент не импортировал react-dnd напрямую.
Передайте DragDropContext и HTML5Backend от родительского к дочернему компоненту.

Добавление уникального ключа в тег DragDropContextProvider решает проблему <DragDropContextProvider backend={HTML5Backend} key={Math. random()}></DragDropContextProvider>

В машинописном тексте я сделал следующий компонент. (спасибо @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;

И использовал такой компонент

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

Добавление уникального ключа в тег DragDropContextProvider решает проблему <DragDropContextProvider backend={HTML5Backend} key={Math. random()}></DragDropContextProvider>

Молодец!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги