[React-dnd] Drag & Drop Card

Darcy Daeseok YU ·2025년 2월 2일

Drag & Drop Card

draggable card

import { FC, memo, useRef } from 'react';
import { ItemTypes } from './ItemTypes';
import { useDrag, useDrop, XYCoord } from 'react-dnd';

const style: React.CSSProperties = {
  border: '1px dashed gray',
  marginBottom: '.5rem',
  backgroundColor: 'white',
  cursor: 'move',

  width: 200,
  aspectRatio: '1/1',
};

interface CardProps {
  id: string;
  text: string;
  index: number;
  moveCard: (dragIndex: number, hoverIndex: number) => void;
}

interface DragItem {
  index: number;
  id: string;
  type: string;
}

export const Card: FC<CardProps> = memo(function Card({
  id,
  text,
  index,
  moveCard,
}) {
  const dragElRef = useRef<HTMLDivElement | null>(null);

  const [{ isDragging, draggedId }, connectDrag, preview] = useDrag({
    type: ItemTypes.CARD,
    item: () => {
      return { id, index };
    },
    collect: (monitor) => ({
      draggedId: monitor.getItem()?.id,
      isDragging: monitor.isDragging(),
    }),
  });

  const [, connectDrop] = useDrop({
    accept: ItemTypes.CARD,
    hover: (item: DragItem, monitor) => {
      if (!dragElRef.current) return;

      const fromIndex = item.index;
      const toIndex = index;

      if (fromIndex === toIndex) return;
      const hoverBoundingRect = dragElRef.current?.getBoundingClientRect();
      const clientOffsetAsMousePosition = monitor.getClientOffset();
      console.log(hoverBoundingRect, clientOffsetAsMousePosition);
      const hoverMiddleX =
        (hoverBoundingRect.right - hoverBoundingRect.left) / 2;
      const hoverMiddleY =
        (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
      const hoverClientX =
        (clientOffsetAsMousePosition as XYCoord).x - hoverBoundingRect.left;
      const hoverClientY =
        (clientOffsetAsMousePosition as XYCoord).y - hoverBoundingRect.top;

      if (
        fromIndex < toIndex &&
        hoverClientY < hoverMiddleY &&
        hoverClientX < hoverMiddleX
      )
        return;

      if (
        fromIndex > toIndex &&
        hoverClientY > hoverMiddleY &&
        hoverClientX > hoverMiddleX
      )
        return;

      moveCard(fromIndex, toIndex);

      // 중요!!
      // Note: we're mutating the monitor item here!
      // Generally it's better to avoid mutations,
      // but it's good here for the sake of performance
      // to avoid expensive index searches.
      item.index = toIndex;
    },
  });

  connectDrop(connectDrag(dragElRef));

  let opacity = 1;
  if (draggedId === id || isDragging) {
    opacity = 0.3;
  }

  return (
    <div ref={dragElRef} style={{ ...style, opacity, position: 'relative' }}>
      <h1>{text}</h1>
      {/* 
      <h1
        style={{
          width: 10,
          height: 10,
          background: 'rgba(255,0,0,.3)',
          position: 'absolute',
          left: 0,
          top: 0,
        }}
      ></h1>

      <h1
        style={{
          width: 10,
          height: 10,
          background: 'rgba(0,255,0,.3)',
          position: 'absolute',
          left: 0,
          top: 0,
        }}
      ></h1> */}

      <h1>
        {' '}
        boundingClientRect?.x :: {dragElRef.current?.getBoundingClientRect().x}
      </h1>
      <h1>
        {' '}
        boundingClientRect?.y :: {dragElRef.current?.getBoundingClientRect().y}
      </h1>
      {/* <h1> boundingClientRect?.x :: {boundingClientRect?.x}</h1>
      <h1> boundingClientRect?.y :: {boundingClientRect?.y}</h1> */}
    </div>
  );
});

Container

import { FC, useCallback, useRef, useState } from 'react';
import { faker } from '@faker-js/faker';
import { Card } from './Card';

interface CardItem {
  id: string;
  text: string;
}

const buildCardData = (): CardItem[] => {
  return Array.from({ length: 1000 }, (_, i) => ({
    id: i.toString(),
    text: faker.internet.username(),
  }));
};

export const Container: FC = () => {
  const [cards, setCards] = useState<CardItem[]>(buildCardData);
  const requestRef = useRef<number | null>(null);

  const moveCard = useCallback((fromIndex: number, toIndex: number) => {
    if (fromIndex === toIndex) return;
    setCards((prevCards) => {
      const updatedCards = [...prevCards];

      const [draggedItem] = updatedCards.splice(fromIndex, 1);
      updatedCards.splice(toIndex, 0, draggedItem);
      return updatedCards;
    });

    // requestRef.current = requestAnimationFrame(() =>
    //   setCards((prev) => [...prev]),
    // );
  }, []);

  // useEffect(() => {
  //   return () => {
  //     if (requestRef.current) cancelAnimationFrame(requestRef.current);
  //   };
  // }, []);

  return (
    <div className='flex flex-wrap p-4'>
      {cards.map((card, idx) => (
        <Card
          key={card.id}
          index={idx}
          id={card.id}
          text={card.text}
          moveCard={moveCard}
        />
      ))}
    </div>
  );
};
profile
React, React-Native https://darcyu83.netlify.app/

0개의 댓글