dndkit
is a powerful library that simplifies the process of building drag-and-drop interfaces in React applications. When combined with TypeScript, it offers type safety, better code organization, and improved developer experience. This blog post will guide you through the fundamental concepts, usage methods, common practices, and best practices of using dndkit
with TypeScript.dndkit
is a set of React hooks and components that provide a flexible and customizable way to implement drag-and-drop functionality. It follows a modular architecture, allowing you to pick and choose the features you need. Some of the key concepts in dndkit
include:
dndkit
provides a set of events such as onDragStart
, onDragMove
, and onDragEnd
that you can use to handle the drag-and-drop lifecycle.TypeScript is a superset of JavaScript that adds static typing to the language. When using dndkit
with TypeScript, you can define types for your draggable items, droppable containers, and data transfer objects. This helps catch errors early in the development process and makes the code more maintainable.
To get started with dndkit
and TypeScript, you first need to install the necessary packages. You can use npm
or yarn
to install dndkit
and its dependencies.
npm install @dnd-kit/core @dnd-kit/sortable
Next, create a new React project with TypeScript support if you haven’t already. You can use create-react-app
with the --template typescript
option.
npx create-react-app my-dnd-app --template typescript
cd my-dnd-app
Here is a simple example of implementing basic drag-and-drop functionality using dndkit
and TypeScript.
import React from 'react';
import {
DndContext,
Draggable,
Droppable,
DragOverlay,
} from '@dnd-kit/core';
import { CSS } from '@dnd-kit/utilities';
const items = ['Item 1', 'Item 2', 'Item 3'];
const App: React.FC = () => {
return (
<DndContext>
<div>
{items.map((item, index) => (
<Draggable key={index} id={index}>
{({ attributes, listeners, setNodeRef, transform, transition }) => (
<div
ref={setNodeRef}
style={{
...CSS.Transform.toString(transform),
transition,
}}
{...attributes}
{...listeners}
>
{item}
</div>
)}
</Draggable>
))}
</div>
<Droppable id="droppable">
{({ attributes, setNodeRef }) => (
<div ref={setNodeRef} {...attributes}>
Drop items here
</div>
)}
</Droppable>
<DragOverlay>
{({ attributes, listeners, setNodeRef, transform, transition }) => (
<div
ref={setNodeRef}
style={{
...CSS.Transform.toString(transform),
transition,
}}
{...attributes}
{...listeners}
>
Dragging...
</div>
)}
</DragOverlay>
</DndContext>
);
};
export default App;
In this example, we have a list of draggable items and a droppable container. When an item is dragged, it is visually represented by the DragOverlay
.
dndkit
also provides a Sortable
component that allows you to create sortable lists. Here is an example of creating a sortable list using dndkit
and TypeScript.
import React, { useState } from 'react';
import {
DndContext,
closestCenter,
getCombinedRect,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import { SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { SortableItem } from './SortableItem';
const items = ['Item 1', 'Item 2', 'Item 3'];
const App: React.FC = () => {
const [listItems, setListItems] = useState(items);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
const handleDragEnd = ({ active, over }) => {
if (active.id !== over?.id) {
setListItems((items) => {
const oldIndex = items.indexOf(active.id as string);
const newIndex = items.indexOf(over?.id as string);
const newItems = [...items];
newItems.splice(oldIndex, 1);
newItems.splice(newIndex, 0, active.id as string);
return newItems;
});
}
};
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
coordinateGetter={getCombinedRect}
>
<SortableContext
items={listItems}
strategy={verticalListSortingStrategy}
>
<div>
{listItems.map((item, index) => (
<SortableItem key={index} id={item} />
))}
</div>
</SortableContext>
</DndContext>
);
};
export default App;
// SortableItem.tsx
import React from 'react';
import { Sortable } from '@dnd-kit/sortable';
export const SortableItem: React.FC<{ id: string }> = ({ id }) => {
return (
<Sortable id={id}>
{({ attributes, listeners, setNodeRef, transform, transition }) => (
<div
ref={setNodeRef}
style={{
transform: transform ? `translate3d(${transform.x}px, ${transform.y}px, 0)` : 'none',
transition,
}}
{...attributes}
{...listeners}
>
{id}
</div>
)}
</Sortable>
);
};
In this example, we use the SortableContext
and SortableItem
components from @dnd-kit/sortable
to create a sortable list. The handleDragEnd
function is used to update the state of the list when an item is dragged and dropped.
You can style draggable and droppable elements using CSS. For example, you can change the appearance of a draggable item when it is being dragged.
.draggable-item {
background-color: #f0f0f0;
padding: 10px;
margin: 5px;
cursor: grab;
}
.draggable-item.dragging {
background-color: #e0e0e0;
cursor: grabbing;
}
.droppable-container {
border: 2px dashed #ccc;
padding: 20px;
}
import React from 'react';
import { Draggable } from '@dnd-kit/core';
const CustomDraggableItem: React.FC<{ id: string; item: string }> = ({ id, item }) => {
return (
<Draggable id={id}>
{({ attributes, listeners, setNodeRef, isDragging }) => (
<div
ref={setNodeRef}
className={`draggable-item ${isDragging ? 'dragging' : ''}`}
{...attributes}
{...listeners}
>
{item}
</div>
)}
</Draggable>
);
};
export default CustomDraggableItem;
dndkit
provides a set of events that you can use to handle the drag-and-drop lifecycle. For example, you can use the onDragStart
, onDragMove
, and onDragEnd
events to perform actions at different stages of the drag-and-drop process.
import React from 'react';
import { DndContext } from '@dnd-kit/core';
const App: React.FC = () => {
const handleDragStart = (event) => {
console.log('Drag started', event.active.id);
};
const handleDragMove = (event) => {
console.log('Drag moving', event.transform);
};
const handleDragEnd = (event) => {
console.log('Drag ended', event.active.id, event.over?.id);
};
return (
<DndContext
onDragStart={handleDragStart}
onDragMove={handleDragMove}
onDragEnd={handleDragEnd}
>
{/* Draggable and Droppable elements */}
</DndContext>
);
};
export default App;
When transferring data between draggable items and droppable containers, it’s a good practice to define types for the data. This helps ensure that the data is consistent and makes the code more readable.
interface DraggableData {
id: string;
name: string;
}
const items: DraggableData[] = [
{ id: '1', name: 'Item 1' },
{ id: '2', name: 'Item 2' },
{ id: '3', name: 'Item 3' },
];
const App: React.FC = () => {
// ...
};
To optimize the performance of your drag-and-drop application, you can use techniques such as memoization and virtualization. For example, you can use React.memo
to prevent unnecessary re-renders of draggable and droppable elements.
import React from 'react';
import { Draggable } from '@dnd-kit/core';
const MemoizedDraggableItem = React.memo(({ id, item }) => {
return (
<Draggable id={id}>
{/* ... */}
</Draggable>
);
});
export default MemoizedDraggableItem;
In this blog post, we have explored the fundamental concepts, usage methods, common practices, and best practices of using dndkit
with TypeScript. By combining the power of dndkit
and TypeScript, you can create robust and maintainable drag-and-drop interfaces in your React applications. Whether you are building a simple to-do list or a complex dashboard, dndkit
and TypeScript provide the tools you need to create a great user experience.