React beautiful DnD drag out of position problem

13 reactjs react-beautiful-dnd

I created a draggable drag and drop table with draggable rows.
I'm using react beautiful-dnd for this.
When I drag a row, the row gets out of position instead on the position of my cursor.
When I drag a row, the row gets position: fixed and some top and left styling.
I suspect thats the issue, but why does it get the wrong numbers, so that its causing to not show on the right position?
This GIF will show the problem.
enter image description here

This is my full code:

import update from "immutability-helper";
import * as React from "react";
import * as ReactDnD from "react-dnd";
import { WithNamespaces, withNamespaces } from "react-i18next";
import { toastr } from "react-redux-toastr";
import * as HttpHelper from "../../httpHelper";
import { FormState } from "../common/ValidatedForm";
import Addtagmodal from "../common/AttributeModal";
import AttributeModal from "./AttributeModal";
import PreviewModal from "./PreviewModal";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
/* import locale from "react-json-editor-ajrm/locale/en"; */
type Props = WithNamespaces & {
  id: number;
  displayName: string;
};

interface Fields {
  columns: any;
}

type State = FormState<Fields> & {
  isLoading: boolean,
  canSave: boolean,
  isSaving: boolean,
  possibleTags: any,
  configTagModalActive: boolean,
  previewModalActive: boolean,
  activeTag: any
};
const getItemStyle = (draggableStyle: any) => ({
  ...draggableStyle
});
const Card = (props: any) => {
  const opacitys = props.isDragging ? 0.3 : 1;

  function findindex(val: any) {
    return props.tags.some((item: any) => val === item.name);
  }
  let select;
  let selectStyle = {};
  let tagInputStyle = {};
  if (props.tags.length == 0 || props.tags.length > 3) {
    selectStyle = { border: "0px", outline: "none", width: "100%", height: "20px", backgroundColor: "transparent", zIndex: 0, float: "left", position: "relative" };
    tagInputStyle = {border: "1px solid #ced4da", height: "auto", width: "400px", padding: "8px", minHeight: "38px", background: "white"};
  }
  else {
    selectStyle = { border: "0px", outline: "none", width: "100%", height: "20px", backgroundColor: "transparent", zIndex: 0, float: "left", top: "-20px", position: "relative" };
    tagInputStyle = {border: "1px solid #ced4da", height: "auto", width: "400px", padding: "8px", minHeight: "38px", background: "white", marginTop: "10px"};
  }
  if (props.tags.length < 4) {
    select =
  <select value="" className="autocomplete-select" style={selectStyle} id={props.index} onChange={props.onaddtag}>
    <option value="" disabled ></option>
    {props.possibleTags.map((i: any) =>

      <option value={i.name} disabled={i.uses == 0 || findindex(i.name) == true ? true : false}>{i.name}</option>

    )}
  </select>;
  }
  else {
    select = undefined;
  }
  return (
        <tr ref={props.provided.innerRef}
        {...props.provided.draggableProps} style={getItemStyle(props.provided.draggableProps.style)} className={(props.indexnr % 2 ? "whiterow" : "grayrow")} key={props.indexnr} data-id={props.indexnr} >
          <td {...props.provided.dragHandleProps} style={{width: "50px", textAlign: "center"}}><i className="fa fa-bars" style={{lineHeight: "40px", fontSize: "24px"}}></i></td>
          <td style={{ textAlign: "center", width: "80px" }}>
            <input
              type="checkbox"
              className="flipswitch"
              id={props.index}
              checked={props.export}
              onChange={props.oncheck}
            />
          </td>
          <td>
            <input
              type="text"
              name="caption"
              id={props.index}
              className="form-control"
              value={props.caption}
              onChange={props.ontextupdate}
            />
          </td>
          <td>
            <input
              type="text"
              name="fieldname"
              id={props.index}
              className="form-control"
              value={props.fieldname}
              onChange={props.ontextupdate}
            />
          </td>
          <td style={{width: "400px"}}>
            <div className="tags-input" style={tagInputStyle}>
            {Object.keys(props.tags).map((key, i) =>
              <div key={key} style={{backgroundColor: "#0753ad", height: "20px", borderRadius: "3px", display: "inline-block", padding: "5px", lineHeight: "12px", float: "left", color: "white", marginRight: "5px", fontSize: "10px", width: "90px", position: "relative", zIndex: 20}}>
                {props.tags[i].name} <i className="fa fa-trash" id={props.index} data-key={i} data-name={props.tags[i].name} onClick={props.ondeletetag} style={{float: "right"}} ></i><i className="fa fa-cog" data-id={i} data-parent={props.index} style={{float: "right", marginRight: "5px"}} onClick={props.onConfigButtonClicked}></i>
              </div>
            )}
            {select}
            </div>
           </td>
          <td style={{ textAlign: "center", width: "80px" }}>
          <button onClick={() => props.ondeleterow(props.index)} type="button" style={{padding : "8px 16px" }} className="btn btn-danger btn-rounded"><i className="fa fa-trash"></i></button>
          </td>
        </tr>
  );
};
const reorder = (list: any, startIndex: any, endIndex: any) => {
  const result = Array.from(list);
  const [removed] = result.splice(startIndex, 1);
  console.log(startIndex, endIndex, removed);
  result.splice(endIndex, 0, removed);

  return result;
};
interface SetColumnsResponse extends HttpHelper.ResponseData { columns: any; }

class CrmConnectorColumns extends React.Component<Props, State> {

  constructor(props: Props) {
    super(props);
    this.moveCard = this.moveCard.bind(this);
    this.oncheck = this.oncheck.bind(this);
    this.ontextupdate = this.ontextupdate.bind(this);
    this.ondeleterow = this.ondeleterow.bind(this);
    this.onaddnewrow = this.onaddnewrow.bind(this);
    this.ondeletetag = this.ondeletetag.bind(this);
    this.onaddtag = this.onaddtag.bind(this);
    this.onConfigButtonClicked = this.onConfigButtonClicked.bind(this);
    this.onPreviewButtonClicked = this.onPreviewButtonClicked.bind(this);
    this.onClosePreview = this.onClosePreview.bind(this);
    this.state = {
      isLoading: true,
      isSaving: false,
      canSave: false,
      errorColor: "danger",
      fields: { columns: {} },
      deleteModalActive: false,
      configTagModalActive: false,
      previewModalActive: false,
      activeTag: {name: "", attributes: [{name: "", value: ""}]},
      possibleTags: [
        {name: "SUBTITLE", status: "new", helptexts: [{language: "nl", helptext: "Dit is de subtitel van een record"}], attributes: [], uses: 1},
        {name: "URL", status: "new", helptexts: [{language: "nl", helptext: "De waarde wordt gezien als html link."}], attributes: [{name: "link", status: "new", helptexts: [{language: "nl", helptext: "De link is deze waarde. Voorbeeld waarde is \"http://www.google.nl?search=[naam]\". op de plaats van \"[naam]\" wordt de waarde van het veld \"naam\" ingevuld."}], uses: undefined}]},
        {name: "TITLE", status: "new", helptexts: [{language: "nl", helptext: "Dit is de hoofdtitel van een record"}], attributes: [], uses: 1},
        {name: "PHONE", status: "new", helptexts: [{language: "nl", helptext: "De waarde wordt gezien telefoonnummer"}], attributes: [], uses: undefined},
        {name: "BUTTON", status: "new", helptexts: [{language: "nl", helptext: "Uiterlijk van een knop"}], attributes: [], uses: undefined},
        {name: "EMAIL", status: "new", helptexts: [{language: "nl", helptext: "De waarde wordt gezien e-mail adres"}], attributes: [], uses: undefined},
        {name: "IMAGE", status: "new", helptexts: [{language: "nl", helptext: "De waarde wordt als afbeelding weergegeven"}], attributes: [], uses: undefined},
        {name: "HTML", status: "new", helptexts: [{language: "nl", helptext: "De waarde wordt gezien als HTML"}], attributes: [{name: "HTML code", status: "new", helptexts: [{language: "nl", helptext: "Vul hier je custom HTML code in. De waarde tussen de [] word vervangen door de data."}], uses: undefined}]}
      ]
    };
    this.onDragEnd = this.onDragEnd.bind(this);
  }
  onDragEnd(result: any) {
    // dropped outside the list
    if (!result.destination) {
      return;
    }
    let newlist = [...this.state.fields.columns];
    newlist = reorder(
      newlist,
      result.source.index,
      result.destination.index
    );
    Object.keys(newlist).forEach((nr) => {
      newlist[parseInt(nr, 10)].index = parseInt(nr, 10);
      });
    this.setState({ fields: { columns: newlist } });
    console.log(this.state.fields.columns);
    this.setState({ canSave: true });




  }
  async componentDidMount() {
    console.log("Start select columns");

    const fields = await HttpHelper.getJson<Fields>(`/connectortypes/${this.props.id}/columns`);
    this.setState(prevState => {
      return update(prevState, {
        fields: { $set: fields },
        isLoading: { $set: false },
      });
    });
    for (let i = 0; i < fields.columns.length; i++) {
      fields.columns[i].index = i;
    }
    this.setState({ fields: { columns: fields.columns } });
    const newlist = [...this.state.possibleTags];
    console.log(newlist);
    for (const column of fields.columns) {
      for (const tags of column.tags) {
        const index = newlist.findIndex(item => item.name == tags.name);
        if (newlist[index].uses > 0) {
          newlist[index].uses = 0;
        }
      }
    }
    this.setState({ possibleTags: newlist });
    console.log(this.state.possibleTags);

  }
  moveCard (index: any, indexnr: any) {
    const cards = this.state.fields.columns;
    const sourceCard = cards.find((card: any) => card.index === index);
    const sortCards = cards.filter((card: any) => card.index !== index);
    sortCards.splice(indexnr, 0, sourceCard);
     Object.keys(sortCards).forEach((nr) => {
    sortCards[nr].index = parseInt(nr, 10);
    });
    this.setState({ fields: { columns: sortCards } });
    console.log(this.state.fields.columns);
    this.setState({ canSave: true });
  }
  oncheck(e: any) {
    const cards = this.state.fields.columns;
    cards[e.target.id].export = e.target.checked;
    this.setState({ fields: { columns: cards } });
    console.log(this.state.fields.columns);
    this.setState({ canSave: true });
  }
  ondeleterow(nr: any) {
    console.log(nr);
    const array = [...this.state.fields.columns]; // make a separate copy of the array
    const arrayCopy = array.filter((row: any) => row.index !== nr);
    this.setState({ fields: { columns: arrayCopy }});
    console.log(this.state.fields.columns);
    this.setState({ canSave: true });
  }
  ontextupdate(e: any) {
    const cards = this.state.fields.columns;
    cards[e.target.id][e.target.name] = e.target.value;
    this.setState({ fields: { columns: cards } });
    this.setState({ canSave: true });
  }
  onaddnewrow() {
    const columnsCopy = this.state.fields.columns;
    columnsCopy.push({index: this.state.fields.columns.length, export: true, editable: false, fieldname: "", caption: "", tags: [] });
    this.setState({ fields: { columns: columnsCopy } });
    this.setState({ canSave: true });
  }
  onDragStart = (e: any) => {
    e.dataTransfer.effectAllowed = "move";
    e.dataTransfer.setData("text/html", e.target.parentNode);
    e.dataTransfer.setDragImage(e.target.parentNode, 20, 20);
  }
  ondragOver(e: any) {
    e.preventDefault();
    const columnsCopy = this.state.fields.columns;
    columnsCopy.pop();
    columnsCopy.push({index: e.target.dataset.id, export: true, editable: false, fieldname: "", caption: "", tags: [] });
    this.setState({ fields: { columns: columnsCopy } });
  }
  onaddtag(e: any) {
    function findindex(element: any) {
      return element.name == e.target.value;
    }
    const index = this.state.possibleTags.findIndex(findindex);
    const array = this.state.fields.columns;

    for (const column of array) {

      if (column.index == e.target.id) {
         const newArray = [ ...array[e.target.id].tags, {name: this.state.possibleTags[index].name, attributes: [] } ];
         array[e.target.id].tags = newArray;
      }
      else {
        const newArray = [...column.tags];
        column.tags = newArray;
      }
      this.setState({ fields: { columns: array } });
    }
    this.setState({ canSave: true });
    const tags = this.state.possibleTags;
    if (tags[index].uses > 0) {
      tags[index].uses = 0;
    }
    this.setState({ possibleTags: tags });
  }
  ondeletetag(e: any) {
    const array = this.state.fields.columns;
    for (const column of array) {
      if (column.index == e.target.id) {
        const newlist = [].concat(array[e.target.id].tags); // Clone array with concat or slice(0)
        newlist.splice(e.target.dataset.key, 1);
        array[e.target.id].tags = newlist;
      }
      else {
        const newArray = [...column.tags];
        column.tags = newArray;
      }
    }
    this.setState({ fields: { columns: array } });
    this.setState({ canSave: true });
    function findindex(element: any) {
      return element.name == e.target.dataset.name;
    }
    const index = this.state.possibleTags.findIndex(findindex);
    const tags = this.state.possibleTags;
    if (tags[index].uses == 0) {
      tags[index].uses = 1;
    }
    this.setState({ possibleTags: tags });
  }
  onUpdateAttribute() {
    this.setState({ configTagModalActive: false });
    this.setState({ canSave: true });
  }
  onPreviewButtonClicked() {
    this.setState({ previewModalActive: true });
  }
  onClosePreview() {
    this.setState({ previewModalActive: false });
  }
  onCancelUpdateAttribute() {
    this.setState({ configTagModalActive: false });
  }
  onConfigButtonClicked(e: any) {
    e.preventDefault();
    this.setState({ activeTag: this.state.fields.columns[e.target.dataset.parent].tags[e.target.dataset.id]});
    this.setState({ configTagModalActive: true, errorMessage: undefined });
    console.log(this.state.activeTag);
  }
  onSubmit = (e: any) => {
    e.preventDefault();
    console.log("Start saving changes");
    this.setState({ isSaving: true }, () => {
      if (this.state.fields) {
        HttpHelper.postJson<SetColumnsResponse>(`/connectortypes/${this.props.id}/columns/`, { columns: this.state.fields.columns }).then((responseData) => {
          if (responseData.responseStatus !== undefined && responseData.responseStatus !== null && responseData.responseStatus.message !== null) {
            this.setState({ isSaving: false, errorMessage: responseData.responseStatus.message });
          }
          else {
            this.setState({ canSave: false, isSaving: false, fields: { columns: responseData.columns } }, () => {
              toastr.success(this.props.displayName, this.props.t("columnsUpdated"));
            });
          }
        });
      }
    });
  }
  public render() {
    const columns = this.state.fields.columns || [] ;
    const { t } = this.props;
    return (
    <form>
      <div className="App">
        <main>
          <button onClick={this.onSubmit} className="btn btn-primary" type="submit" style={{float: "right"}} disabled={!this.state.canSave || this.state.isSaving}>{this.state.isSaving ? <i className="fa fa-spinner fa-spin"></i> : ""} {this.props.t("update")}</button><br/><br/>
          <DragDropContext onDragEnd={this.onDragEnd}>
          <Droppable droppableId="droppable">
          {(provided: any) => (
          <table ref={provided.innerRef} className="col-8 table columns" style={{border: "1px solid #dee2e6"}} >
          <thead className="thead-dark" style={{border: "1px solid #1b2847"}}>
          <tr>
          <th colSpan={2}>
            <button onClick={this.onaddnewrow} type="button" style={{padding : "8px 16px" }} className="btn btn-primary btn-rounded"><i className="fa fa-plus"></i> </button>
          </th>
           <th>{t("displayname")}</th>
           <th>Element</th>
           <th>Tags</th>
           <th>
             <button onClick={this.onPreviewButtonClicked} type="button" className="btn btn-primary"  style={{float: "right"}} >Preview</button>
          </th>
          </tr>
        </thead>
        <tbody>
            {Object.keys(columns).map((key, i) => (
              <Draggable key={i} draggableId={key} index={i}>
              {(provided) => (
             <Card
             key={columns[i].index}
             indexnr={i}
             oncheck={this.oncheck}
             ontextupdate={this.ontextupdate}
             ondeleterow={this.ondeleterow}
             ondeletetag={this.ondeletetag}
             onaddtag={this.onaddtag}
             possibleTags={this.state.possibleTags}
             onConfigButtonClicked={this.onConfigButtonClicked}
             onPreviewButtonClicked={this.onPreviewButtonClicked}
             onClosePreview={this.onClosePreview}
             provided={provided}
             {...columns[i]}
           />
           )}
           </Draggable>
           ))}
            </tbody>
          </table>
          )}
         </Droppable>
      </DragDropContext>
        </main>
      </div>
      <AttributeModal
        startAction={this.onUpdateAttribute.bind(this)}
        isOpen={this.state.configTagModalActive}
        headerText={t("header")}
        activeTag={this.state.activeTag}
        addText={t("close")}
        possibleTags={this.state.possibleTags} >
      </AttributeModal>

      <PreviewModal
        startAction={this.onClosePreview.bind(this)}
        isOpen={this.state.previewModalActive}
        headerText="Preview"
        addText={t("close")}
        columns={this.state.fields.columns} >
      </PreviewModal>
    </form>
    );
  }
}

export default withNamespaces("crmConnectorColumns")(CrmConnectorColumns);
Run Code Online (Sandbox Code Playgroud)

Does anyone know why my draggable item gets out of position?
The only css I'm using is bootstrap and the ones in my code.

ozg*_*zer 19

当我尝试显示react-beautiful-dnd内部时,我遇到了相同的位置问题react-modal,我通过将这些 CSS 添加到可拖动项目找到了解决方案。

.draggable {
  top: auto !important;
  left: auto !important;
}
Run Code Online (Sandbox Code Playgroud)

  • 离开这个答案,这对我有用... [data-rbd-draggable-id] { left: auto !important; 顶部:自动!重要;} (2认同)

and*_*dre 8

我有同样的问题,我想通了!:-)

解决方案可以在这里找到:https : //github.com/atlassian/react-beautiful-dnd/blob/master/docs/patterns/using-a-portal.md

基本上,当库position: fixed像 OP 一样使用时,有时会出现一些意想不到的后果 - 在这些情况下,您需要使用门户。

我通过查看此处的门户示例使其工作:https : //github.com/atlassian/react-beautiful-dnd/blob/master/stories/src/portal/portal-app.jsx

由于此评论找到了解决方案:https : //github.com/atlassian/react-beautiful-dnd/issues/485#issuecomment-385816391

  • 请更新链接 https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/patterns/using-a-portal.md,它们会导致 404。 (4认同)

小智 7

我在使用react-beautiful-dnd. 就我而言,原因是我有两个具有相同 ID 的项目。


小智 6

transform通过从父元素中删除来修复

如果任何父元素的transform规则设置为除 以外的任何内容none,但只要will-change: transform在父元素上设置规则也会导致此问题

我发现,Chrome 开发工具在这种情况下可以提供很大的帮助,可以找到具有以下规则的父元素:

转到元素 -> 样式 -> 计算 -> 过滤器transform并搜索所有父元素以查找可能导致此问题的任何规则

通过重新调整父级来修复<Draggable />

如果无法删除此规则,则该库也有一个解决方案,因为您可以在 React 中重新设置父级使用门户(但他们不建议这样做)