kanban-column/ ├── src/ │ ├── components/ │ │ ├── KanbanColumn.tsx │ │ └── KanbanCard.tsx │ ├── hooks/ │ │ └── useOptimisticDragAndDrop.ts │ ├── api/ │ │ └── reorderCards.ts │ └── types.ts └── tests/ └── KanbanColumn.test.tsx
// src/hooks/useOptimisticDragAndDrop.ts (core logic excerpt) import { useState, useCallback, useRef } from 'react'; import { Card } from '../types'; import { reorderCards } from '../api/reorderCards'; // Mocked API call
export const useOptimisticDragAndDrop = (initialCards: Card[], apiEndpoint: string) => { const [cards, setCards] = useState<Card[]>(initialCards); const dragItem = useRef<number | null>(null); const originalCardsRef = useRef<Card[]>(initialCards); // Store for rollback
const handleDragStart = useCallback((e: React.DragEvent<HTMLDivElement>, position: number) => { dragItem.current = position; e.dataTransfer.effectAllowed = 'move'; }, []);
const handleDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>, position: number) => { e.preventDefault(); if (dragItem.current === null || dragItem.current === position) return;
const newCards = [...cards]; const draggedCard = newCards[dragItem.current]; newCards.splice(dragItem.current, 1); newCards.splice(position, 0, draggedCard);
dragItem.current = position; // Update the 'current' position of the dragged item setCards(newCards); // Optimistic UI update }, [cards]);
const handleDragEnd = useCallback(async () => { if (dragItem.current === null) return;
const updatedOrder = cards.map(card => card.id); originalCardsRef.current = initialCards; // Snapshot initial state for rollback
try { await reorderCards(apiEndpoint, updatedOrder); // Simulate API call } catch (error) { console.error('Failed to reorder cards:', error); setCards(originalCardsRef.current); // Rollback on failure } finally { dragItem.current = null; } }, [cards, apiEndpoint, initialCards]);
return { cards, handleDragStart, handleDragEnter, handleDragEnd }; };
// Accessibility Notes
- ARIA roles
list and listitem provide semantic structure for screen readers. aria-grabbed attribute on draggable cards communicates their drag state.tabIndex={0} on cards allows keyboard focus and activation of drag-and-drop via Space/Enter keys, with arrow keys for navigation.- Visual focus indicators are implemented for keyboard users.
// Performance Notes
useCallback and useRef hooks are used to prevent unnecessary re-renders of components and memoize expensive computations.- Unique
key props on list items ensure efficient DOM updates by React's reconciliation algorithm. - Optimistic UI updates provide immediate feedback, enhancing perceived performance and responsiveness, especially during network latency.
- State management within the
useOptimisticDragAndDrop hook is optimized to trigger minimal re-renders, impacting only the relevant parts of the UI.