最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How do I make multiple sortable containers with @dnd-kit? - Stack Overflow

programmeradmin3浏览0评论

I'm trying to learn to use @dnd-kit with React for a couple of projects. The toolkit is clearly very powerful, versatile, and robust. However, the documentation can be unclear at times. Additionally, they have an amazing set of different examples, but there's no code associated with examples to show exactly how they made them...

The one I specifically want to replicate is the multiple sortable containers. Can someone talk me through how this actually works?

I've been playing around for a while have a version that's pretty close, but some things are still not right... Here's what I have at the moment:

Here's my App.js:

import './App.css';
import { useState } from 'react'
import {closestCenter, DndContext} from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy,} from '@dnd-kit/sortable';
import Lane from './Components/Lane';

function App() {
  const [todoItems, setTodoItems] = useState([
    "Item 1",
    "Item 2",
    "Item 3",
    "Item 4",
    "Item 5"
  ]);

  const [doneItems, setDoneItems] = useState([
    "Item 6",
    "Item 7",
    "Item 8",
    "Item 9",
    "Item 10"
  ]);

  return (
    <DndContext onDragOver={handleDragOver} onDragEnd={handleDragEnd} collisionDetection={closestCenter}>
      <div className="App" style={{display: 'flex', justifyContent: 'center', width: '100vw', height: '100vh', backgroundColor:'slateblue', padding: '2rem', boxSizing: 'border-box'}}>
        <SortableContext items={todoItems} strategy={verticalListSortingStrategy}>
          <Lane title="ToDo" items={todoItems} />
        </SortableContext>
        <SortableContext items={doneItems} strategy={verticalListSortingStrategy}>
          <Lane title="Done" items={doneItems} />
        </SortableContext>
      </div>
    </DndContext>
  );

  function handleDragOver (e) {
    const {active, over} = e;

    if(over.id === 'Done' && !doneItems.includes(active.id)) {
      setDoneItems([...doneItems, active.id])
      setTodoItems(todoItems.filter(item => item !== active.id))
    }

    if(over.id === 'ToDo' && !todoItems.includes(active.id)) {
      setTodoItems([...todoItems, active.id])
      setDoneItems(doneItems.filter(item => item !== active.id))
    }
  }
  
  function handleDragEnd (e) {
    const {active, over} = e;
    const container = over.id;
    const title = active.data.current?.title ?? "";
    const index = active.data.current?.index ?? 0;
    const parent = active.data.current?.parent ?? "ToDo";
  }
}

export default App;

And my Lane.js

import {useDroppable} from '@dnd-kit/core';
import Item from './Item';

const Lane = ({ title, items }) => {
    const {isOver, setNodeRef} = useDroppable({
        id: title,
      });
    const style = {
        backgroundColor: 'slategray',
        opacity: isOver ? '0.8' : '1',
        width: '300px', height: '500px', margin: '2rem'
    };

    return (
        <div ref={setNodeRef} style={style}>
            <h3>{title}</h3>
            {items.map((item, index) => {
                return <Item key={index} title={item} index={index} parent={title} />
            })}
        </div>
    )
}

export default Lane

Lastly my Item.js

import React from 'react';
import {useSortable} from '@dnd-kit/sortable';
import { CSS } from "@dnd-kit/utilities";

const Item = ({title, index, parent}) => {
    const {attributes, listeners, setNodeRef, transform} = useSortable({
        id: title,
        data: {
            title,
            index,
            parent,
        }
      });
      const style = {
        transform: transform ? CSS.Translate.toString(transform) : undefined,
        borderTop: '1px solid blue',
        padding: '1rem',
        boxShadow: '0px 0px 5px 2px #2121213b'
      }
    
      
    return (
        <div ref={setNodeRef} style={style} {...listeners} {...attributes}>
          {title}
        </div>
  )
}

export default Item

The documentation says:

In this example, we would use the onDragOver callback of DndContext to detect when a draggable element is moved over a different container to insert it in that new container while dragging.

So I did something similar to that with my dragOver function. I am aware that my dragEnd function isn't pleted yet - I was trying to get the first part working first.

Thanks for your help!

I'm trying to learn to use @dnd-kit with React for a couple of projects. The toolkit is clearly very powerful, versatile, and robust. However, the documentation can be unclear at times. Additionally, they have an amazing set of different examples, but there's no code associated with examples to show exactly how they made them...

The one I specifically want to replicate is the multiple sortable containers. Can someone talk me through how this actually works?

I've been playing around for a while have a version that's pretty close, but some things are still not right... Here's what I have at the moment:

Here's my App.js:

import './App.css';
import { useState } from 'react'
import {closestCenter, DndContext} from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy,} from '@dnd-kit/sortable';
import Lane from './Components/Lane';

function App() {
  const [todoItems, setTodoItems] = useState([
    "Item 1",
    "Item 2",
    "Item 3",
    "Item 4",
    "Item 5"
  ]);

  const [doneItems, setDoneItems] = useState([
    "Item 6",
    "Item 7",
    "Item 8",
    "Item 9",
    "Item 10"
  ]);

  return (
    <DndContext onDragOver={handleDragOver} onDragEnd={handleDragEnd} collisionDetection={closestCenter}>
      <div className="App" style={{display: 'flex', justifyContent: 'center', width: '100vw', height: '100vh', backgroundColor:'slateblue', padding: '2rem', boxSizing: 'border-box'}}>
        <SortableContext items={todoItems} strategy={verticalListSortingStrategy}>
          <Lane title="ToDo" items={todoItems} />
        </SortableContext>
        <SortableContext items={doneItems} strategy={verticalListSortingStrategy}>
          <Lane title="Done" items={doneItems} />
        </SortableContext>
      </div>
    </DndContext>
  );

  function handleDragOver (e) {
    const {active, over} = e;

    if(over.id === 'Done' && !doneItems.includes(active.id)) {
      setDoneItems([...doneItems, active.id])
      setTodoItems(todoItems.filter(item => item !== active.id))
    }

    if(over.id === 'ToDo' && !todoItems.includes(active.id)) {
      setTodoItems([...todoItems, active.id])
      setDoneItems(doneItems.filter(item => item !== active.id))
    }
  }
  
  function handleDragEnd (e) {
    const {active, over} = e;
    const container = over.id;
    const title = active.data.current?.title ?? "";
    const index = active.data.current?.index ?? 0;
    const parent = active.data.current?.parent ?? "ToDo";
  }
}

export default App;

And my Lane.js

import {useDroppable} from '@dnd-kit/core';
import Item from './Item';

const Lane = ({ title, items }) => {
    const {isOver, setNodeRef} = useDroppable({
        id: title,
      });
    const style = {
        backgroundColor: 'slategray',
        opacity: isOver ? '0.8' : '1',
        width: '300px', height: '500px', margin: '2rem'
    };

    return (
        <div ref={setNodeRef} style={style}>
            <h3>{title}</h3>
            {items.map((item, index) => {
                return <Item key={index} title={item} index={index} parent={title} />
            })}
        </div>
    )
}

export default Lane

Lastly my Item.js

import React from 'react';
import {useSortable} from '@dnd-kit/sortable';
import { CSS } from "@dnd-kit/utilities";

const Item = ({title, index, parent}) => {
    const {attributes, listeners, setNodeRef, transform} = useSortable({
        id: title,
        data: {
            title,
            index,
            parent,
        }
      });
      const style = {
        transform: transform ? CSS.Translate.toString(transform) : undefined,
        borderTop: '1px solid blue',
        padding: '1rem',
        boxShadow: '0px 0px 5px 2px #2121213b'
      }
    
      
    return (
        <div ref={setNodeRef} style={style} {...listeners} {...attributes}>
          {title}
        </div>
  )
}

export default Item

The documentation says:

In this example, we would use the onDragOver callback of DndContext to detect when a draggable element is moved over a different container to insert it in that new container while dragging.

So I did something similar to that with my dragOver function. I am aware that my dragEnd function isn't pleted yet - I was trying to get the first part working first.

Thanks for your help!

Share Improve this question asked Jun 13, 2023 at 21:36 DJFancyAlDJFancyAl 611 silver badge4 bronze badges 1
  • 1 Sorry, I'm just looking at the library myself so I can't answer the question, however, the code to the examples is in the repo github./clauderic/dnd-kit/tree/master/stories – Tim Fairbrother Commented Jul 31, 2023 at 2:56
Add a ment  | 

1 Answer 1

Reset to default 2

I'm also very new to dnd-ket, so take my answer with a bit of skepticism, this could be a bit of the blind leading the blind. That said, I've been investigating how to do something similar and here's what I've found:

As you currently have it written, both the column and each item in the column is a Droppable. This means that when dragging something around, the dragOver event can fire on either one, columns or items. See this relevant screenshot from the docs:

The issue is that your code only works if the user drags onto the column, as determined by the closestCenter algorithm you have selected. Relevant code in handleDragOver:

    [...]

    if(over.id === 'Done' && !doneItems.includes(active.id)) {
      setDoneItems([...doneItems, active.id])
      setTodoItems(todoItems.filter(item => item !== active.id))
    }

    [...]

Replacing over.id === 'Done' with (over.id === 'Done' || doneItems.includes(over.id)) (and again for todo a few lines down) seems to make it behave more resiliently, it now handles drag over events regardless of whether they are over the column or the item.

If you look at the latest example implementation, you'll see they do it slightly differently. They provide a custom collision detection function to ensure it will only trigger drag overs from items to items, or columns to columns.

They also do some shenanigans with state -- My impression is that the standard procedure for dndkit is to not alter the underlying state until the dragEnd event happens.... EXCEPT for the case of multiple containers. It's the only example I see where altering draggable order in a dragOver event is remended. Maybe I'm wrong about that, but it means they have to do some extra work saving what the original state was in case the user wants to cancel for whatever reason (for example by pressing esc).

I'm not sure if I'm understanding all this correctly, I'm not sure what the best practices are here. It seems to me this tool does not have a canonical way to build multiple sortable containers without getting your hands a bit dirty, but maybe I'm missing something.

发布评论

评论列表(0)

  1. 暂无评论