How to update the State on a DnD Component

2.4k Views Asked by At

I'm currently trying to use a Drag & Drop Library called dnd-kit and its hook called useSortable.

So far I did achieved to make everything draggable the way I like, unfortunately somehow its not possible for me to update the state accordingly to the dragging.

Attached you can find also an example of my code.

I'm glad for every suggestion to solve my problem :)

 
import React, { useState } from "react";
import "./styles.css";
import { DndContext } from "@dnd-kit/core";
import { arrayMove, SortableContext, useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";

function SortableItem({ item }) {
  const { id, name } = item;
  const {
    attributes,
    listeners,
    setNodeRef,
    transform,
    transition
  } = useSortable({ id: id });

  const style = {
    transform: CSS.Transform.toString(transform),
    transition
  };

  return (
    <li ref={setNodeRef} style={style} {...attributes} {...listeners} draggable>
      Movement for {name}
    </li>
  );
}

export default function App() {
  const [items, setItems] = useState([
    { id: 1, name: "Items One" },
    { id: 2, name: "Item 2" },
    { id: 3, name: "Items 3" }
  ]);

  const handleDragEnd = (event) => {
    console.log(event);
    const { active, over } = event;

    if (active.id !== over.id) {
        setItems((items) => {
            console.log(items);
            const oldIndex = items.indexOf(active.id);
            const newIndex = items.indexOf(over.id);
            const newItemsArray = arrayMove(items, oldIndex, newIndex);
            return newItemsArray;
        });
    }
};

  console.log(items);

  return (
    <div className="App">
      <h1>Sorting Example</h1>

      <DndContext onDragEnd={handleDragEnd}>
        <SortableContext items={items}>
          {items.map((item) => (
            <SortableItem key={item.id} item={item} />
          ))}
        </SortableContext>
      </DndContext>
    </div>
  );
}


Edit sharp-paper-qbrrh

2

There are 2 best solutions below

0
On

You're main issue is in these lines:

const oldIndex = items.indexOf(active.id);
const newIndex = items.indexOf(over.id);

Since you're using an array of objects and not just an array, you'll need to find the index through find as well and not just indexOf to get its index. Like so:

const oldItem = items.find({ id }) => id === active.id)
const newItem = items.find({ id }) => id === over.id)
const oldIndex = items.indexOf(oldItem);
const newIndex = items.indexOf(newItem);
const newItemsArray = arrayMove(items, oldIndex, newIndex);

If you're using typescript, any linting can complain that oldItem or newItem can be undefined and indexOf doesn't accept undefineds, so you can just use ! to force it because you know that the item will exist (usually), e.g.: const oldIndex = items.indexOf(oldItem!);

Update: or even better/cleaner using findIndex as suggested by @Ashiq Dey:

const oldIndex = items.findIndex({ id }) => id === active.id)
const newIndex = items.findIndex({ id }) => id === over.id)
const newItemsArray = arrayMove(items, oldIndex, newIndex);
1
On

The problem is with how you are trying to match id in indexOf method. if your items array looks like this

[
  {id:1,name:"Item 1"},
  {id:3,name:"Item 2"},
  {id:4,name:"Item 3"}
]

then you should use the below code to make the match for respective id f.id === active.id but you were just passing the value of id and not making a conditional match.

function handleDragEnd(event) {
    const { active, over } = event;

    if (active.id !== over.id) {
        setItems((items) => {
            const oldIndex = items.findIndex(f => f.id === active.id);
            const newIndex = items.findIndex(f => f.id === over.id);
            return arrayMove(items, oldIndex, newIndex);
        });
    }
}