Trying to implement dnd-kit functionality for every row inside a table that is rendered inside of a modal.
I am trying to make the row that is created with map() method draggable and droppable inside the table. I have also created two simple components just to check if the dnd-kit is working at all and it does. But in that example I am just mapping over state that contains strings so the items prop that <SortableContext>
is asking for is easy to pass. I am not sure if that is what is causing the problem in my app. If anyone can give me any suggestions or solution I would be very grateful!
This is the sortable item from the simple example that works:
import { useSortable } from "@dnd-kit/sortable";
import {CSS} from "@dnd-kit/utilities";
import Box from '@mui/material/Box';
function SortableItem(props) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition
} = useSortable({id: props.id});
const style = {
transform: CSS.Transform.toString(transform),
transition
}
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
<Box sx={{margin: "5px", border: "1px solid gray"}}>
<button>button</button>
{props.id}
</Box>
</div>
)
};
export default SortableItem;
This is the second piece of code that creates the working example:
import {useState} from 'react';
import Box from '@mui/material/Box';
import { DndContext, closestCenter } from "@dnd-kit/core";
import { arrayMove, SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import SortableItem from "./sortableItem.js";
function DndKitTest() {
const [people, setPeople] = useState(["Luka Jovicic", "Dejan Markic", "Branko Kovacevic", "Nemanja Djokic"]);
return (
<DndContext
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<Box sx={{ padding: "5px", witdh: 10, border: "1px solid gray", margin: 5 }}>
<h3>List of people!</h3>
<SortableContext
items={people}
strategy={verticalListSortingStrategy}
>
{people.map(person => <SortableItem key={person} id={person} />)}
</SortableContext>
</Box>
</DndContext>
);
function handleDragEnd(event) {
console.log("Drag end called");
const {active, over} = event;
console.log("ACTIVE: " + active.id);
console.log("OVER: " + over.id);
if(active.id !== over.id) {
setPeople((items) => {
const activeIndex = items.indexOf(active.id);
const overIndex = items.indexOf(over.id);
return arrayMove(items, activeIndex, overIndex)
});
}
}
}
export default DndKitTest;
As for my APP I will provide two pieces of code that are using dnd-kit:
import React, { useMemo, Fragment, useState, useEffect } from 'react';
import TableRow from '@mui/material/TableRow';
import TableCell from '@mui/material/TableCell';
import IconButton from '@mui/material/IconButton';
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import CloseOutlinedIcon from '@mui/icons-material/CloseOutlined';
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
function SortingRow({ sortOrder, fields, moveRowUp, moveRowDown, handleOrderChange, resetSingleSort, rowItems, setRowItems, handleRowsChange }) {
const [idVal, setIdVal] = useState("");
const rows = sortOrder.map((item, index) => {
const ID = `sortable-row-${index}`;
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: ID });
const style = {
transform: CSS.Transform.toString(transform),
transition
};
useEffect(() => {
setIdVal(ID);
}, [ID]);
return (
<TableRow key={index} ref={setNodeRef} style={style} {...attributes} {...listeners}>
<TableCell>
{fields.find((f) => f.name === item.field)?.label}
<IconButton onClick={() => moveRowUp(item.field)}>
<ArrowUpwardIcon />
</IconButton>
<IconButton onClick={() => moveRowDown(item.field)}>
<ArrowDownwardIcon />
</IconButton>
</TableCell>
<TableCell>
<Box>
<Button onClick={() => handleOrderChange(item.field, item.direction)}>
{item.direction}
</Button>
<IconButton onClick={() => resetSingleSort(item.field)}>
<CloseOutlinedIcon />
</IconButton>
</Box>
</TableCell>
</TableRow>
);
});
useEffect(() => {
handleRowsChange(rows, idVal);
}, []);
// console.log('CEO ROW IZ sortingROW', rows);
// console.log('ID IZ sortingROW', id);
// console.log('STATE ID IZ sortingRow', idVal);
return <>{rows}</>;
}
export default SortingRow;
And the second one:
import React, { useState, useMemo } from 'react';
import PropTypes from 'prop-types';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
import IconButton from '@mui/material/IconButton';
import Button from '@mui/material/Button';
import CloseOutlinedIcon from '@mui/icons-material/CloseOutlined';
import Box from '@mui/material/Box';
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import { DndContext, closestCenter } from "@dnd-kit/core";
import { arrayMove, SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import SortingRow from "./sortingRow.js";
function TableComponent({ sortOrder, setSortOrder, fields, moveRowUp, moveRowDown, handleOrderChange, resetSingleSort, onRowsUpdated, rows, id }) {
const [rowsVal, setRowsVal] = useState([]);
const [activeRowId, setActiveRowId] = useState("");
const [overRowId, setOverRowId] = useState("");
const handleRowsChange = (rows, idVal) => {
setRowsVal(rows);
};
console.log("rowsVal", rowsVal);
function handleDragEnd(event) {
console.log("Drag end called!");
const { active, over } = event;
console.log("ACTIVE: " + active.id);
console.log("OVER: " + over.id);
setActiveRowId(active.id);
setOverRowId(over.id);
if (active.id !== over.id) {
setSortOrder((items) => {
const activeIndex = items.indexOf(active.id);
const overIndex = items.indexOf(over.id);
return arrayMove(items, activeIndex, overIndex);
});
}
};
return (
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Field</TableCell>
<TableCell>Order</TableCell>
</TableRow>
</TableHead>
<TableBody>
<SortableContext items={rowsVal} strategy={verticalListSortingStrategy}>
<SortingRow
sortOrder={sortOrder}
fields={fields}
moveRowUp={moveRowUp}
moveRowDown={moveRowDown}
handleOrderChange={handleOrderChange}
resetSingleSort={resetSingleSort}
handleRowsChange={handleRowsChange}
activeRowId={activeRowId}
overRowId={overRowId}
/>
</SortableContext>
</TableBody>
</Table>
</DndContext>
);
}
TableComponent.propTypes = {
sortOrder: PropTypes.array.isRequired,
fields: PropTypes.array.isRequired,
moveRowUp: PropTypes.func.isRequired,
moveRowDown: PropTypes.func.isRequired,
handleOrderChange: PropTypes.func.isRequired,
resetSingleSort: PropTypes.func.isRequired,
};
export default TableComponent;
I think that passing the items prop inside the is being passed the wrong way? Maybe all the different buttons and functions inside the SortingRow
are causing the problem? I am pretty sure that ID-s are not the problem, because when I try to drag the row at index 0 over the row that is on index 1 everything console.log-s correctly.