3

I am using react-beautiful-dnd. I have it working, except when I drag and drag an item into one of my lists, the item is positioned incorrrectly, has a short delay, then jumps to the correct position.

Here is what it looks like: Link to issue

As you can see, after the item is dropped in a list, the item readjusts itself to fit within the div.

Here is the code for the item:

        import React, { useState } from "react";
    import styled from "styled-components";
    import { Draggable } from "react-beautiful-dnd";
    
    const Container = styled.div`
      margin: 0 0 8px 0;
      background-color: rgba(140, 240, 255);
    `;
    
    const Title = styled.div`
      font-size: 1.5rem;
    `;
    
    const Gradient = styled.div`
      background: black;
      height: 2px;
      margin: 0.5rem;
    `;
    
    const Description = styled.div`
      font-size: 1rem;
    `;
    
    const Ticket = ({ ticket, setCategories, id, index }) => {
      const [isDeleted, setIsDeleted] = useState(false);
      const handleDelete = (e) => {
        e.preventDefault();
        fetch(`/tickets/${ticket.id}`, {
          method: "DELETE",
        }).then(
          fetch("/categories")
            .then((r) => r.json())
            .then(setCategories)
        );
        setIsDeleted(true);
      };
    
      return (
        <Draggable draggableId={id.toString()} index={index}>
          {(provided, snapshot) =>
            isDeleted ? null : (
              <div
                ref={provided.innerRef}
                {...provided.draggableProps}
                {...provided.dragHandleProps}
              >
                <Container
                  style={{
                    backgroundColor: snapshot.isDragging
                      ? "aquamarine"
                      : "rgba(140, 240, 255)",
                  }}
                >
                  <Title>{ticket.title}</Title>
                  <Gradient></Gradient>
                  <Description>{ticket.description}</Description>
                  <button onClick={handleDelete}>Delete</button>
                </Container>
              </div>
            )
          }
        </Draggable>
      );
    };
    
    export default Ticket;

And here is for the list:

import React, { useState } from "react";
import styled from "styled-components";
import Ticket from "./Ticket";
import { Droppable } from "react-beautiful-dnd";
import { transformData } from "./Categories";

const Container = styled.div`
  background-color: rgba(255, 255, 255, 0.8);
  border-radius: 0.25em;
  box-shadow: 0 0 0.25em rgba(0, 0, 0, 0.25);
  text-align: center;
  width: 20rem;
  font-size: 1.5rem;
  padding: 4px;
  margin: 1rem;
`;

const Gradient = styled.div`
  background: black;
  height: 2px;
  margin: 1rem;
`;

const FormContainer = styled.div`
  margin: 1rem;
  border: 1px solid black;
  backgroundColor: rgba(140, 240, 255);
`;

const Button = styled.button`
  margin-left: 1rem;
`;

const DropDiv = styled.div`
  min-height: 50vh;
  padding: 4px;
`;

const Category = ({ category, user, setCategories, id }) => {
  const [isClicked, setIsClicked] = useState(false);
  const [title, setTitle] = useState("");
  const [description, setDescription] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    const newTicket = {
      title: title,
      description: description,
      user_id: user.id,
      category_id: id,
    };
    fetch("/tickets", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(newTicket),
    }).then(
      fetch("/categories")
        .then((r) => r.json())
        .then(transformData)
        .then(setCategories)
    );
    setIsClicked(false);
  };

  return (
    <Container>
      {category.title}
      <Button onClick={() => setIsClicked(!isClicked)}>Add</Button>
      <Gradient></Gradient>
      {isClicked ? (
        <FormContainer>
          <form onSubmit={handleSubmit}>
            <label>Title</label>
            <input onChange={(e) => setTitle(e.target.value)}></input>
            <label>Description</label>
            <input onChange={(e) => setDescription(e.target.value)}></input>
            <button type="submit">Submit</button>
          </form>
        </FormContainer>
      ) : null}
      <Droppable droppableId={id.toString()}>
        {(provided, snapshot) => (
          <DropDiv
            {...provided.droppableProps}
            ref={provided.innerRef}
            style={{
              background: snapshot.isDraggingOver ? "lightblue" : "",
            }}
          >
            {category.tickets.map((ticket, index) => {
              return (
                <Ticket
                  ticket={ticket}
                  key={ticket.id}
                  setCategories={setCategories}
                  id={ticket.id}
                  index={index}
                />
              );
            })}
            {provided.placeholder}
          </DropDiv>
        )}
      </Droppable>
    </Container>
  );
};

export default Category;

I have tried flexbox styling and messed with margin and padding. If i remove the margin and padding it seems to go away, but in beautiful-dnd examples they all have space between items and theres no delay like this. Does anyone have any ideas?

Brad Bieselin
  • 107
  • 1
  • 11

3 Answers3

2

It looks like the placeholder might not have the 8px bottom margin that the rest of the Draggables have.

You'll notice that when you pick up something (without changing it's position) from somewhere other than the end of the list, the list will shift up a bit right away, and when you drop something at the end of a list, you don't see the issue.

The placeholder gets its margins from the item being dragged. You can see this happening by looking at the inline styles on the placeholder element that appears at the end of the droppable while you are dragging.

So, you might want to try putting the provided innerRef, draggableProps, and dragHandleProps on the Ticket Container itself instead of a parent div, as it's possible that because they are on a different element, react-beautiful-dnd isn't taking the margins in to account.

1

The delay could be caused because your list is changing size while dragging an element, and this provokes the library to recalculate and animate slower.

The solution is to avoid this size change while dragging.

The causes of the issue could be:

---- Cause A ----

The {provided.placeholder} auto inserted in the DOM while dragging doesn't have the same margins/padding that the other Draggable elements

Solution

Be sure to add the styles that separate the elements (margin/padding) to the element that you are applying the provided innerRef, draggableProps, and dragHandleProps because in this way the {provided.placeholder} will inherit those styles.

---- Cause B ----

You are using flexbox or css-grid with a gap property to separate the elements.

Solution

Stop using gap and just add margin to the element with provided innerRef, draggableProps, and dragHandleProps (do not use inline styles but css classes)

Extra: To confirm that size change is the cause

Drag some elements from somewhere other than the end of the list and notice how another element will move up a bit. Also, when you drop something at the end of a list, you don't see the problem.

Juanma Menendez
  • 17,253
  • 7
  • 59
  • 56
0

I was having this same issue- but as seen in the docs, inline styling with provided props did the trick for me:

const ListItem = ({ item, index }) => {
  return (
    <Draggable draggableId={item.id} className="draggableItem" index={index}>
      {(provided, snapshot) => {
        return (
          <div
            ref={provided.innerRef}
            snapshot={snapshot}
            {...provided.draggableProps}
            {...provided.dragHandleProps}
            style={{
              userSelect: "none",
              padding: 12,
              margin: "0 0 8px 0",
              minHeight: "50px",
              borderRadius: "4px",
              backgroundColor: snapshot.isDragging
                ? "rgb(247, 247, 247)"
                : "#fff",
              ...provided.draggableProps.style,
            }}
          >
            <div className="cardHeader">Header</div>
            <span>Content</span>
            <div className="cardFooter">
              <span>{item.content}</span>
              <div className="author">
                {item.id}
                <img className="avatar" />
              </div>
            </div>
          </div>
        );
      }}
    </Draggable>
  );
};