[React] Flip with [data-] attribute

Darcy Daeseok YU ·2026년 8월 6일

Flip with [data-] attribute

import { useLayoutEffect, useRef, useState } from 'react';

function useFlipList<T>(deps: T) {
  const containerRef = useRef<HTMLElement | null>(null);
  const positionsRef = useRef<Map<string, number> | null>(null);

  // 순서를 바꾸기 직전에 호출 (setState 전에)
  const capture = () => {
    const container = containerRef.current;
    if (!container) return;
    const positions = new Map<string, number>();
    container.querySelectorAll<HTMLElement>('[data-row-id]').forEach((el) => {
      positions.set(el.dataset.rowId ?? '', el.getBoundingClientRect().top);
    });
    positionsRef.current = positions;
  };

  useLayoutEffect(() => {
    const previous = positionsRef.current;
    if (!previous) return;
    positionsRef.current = null;
    const container = containerRef.current;
    if (!container) return;
    container.querySelectorAll<HTMLElement>('[data-row-id]').forEach((el) => {
      const before = previous.get(el.dataset.rowId ?? '');
      if (before === undefined) return;
      const delta = before - el.getBoundingClientRect().top;
      if (delta === 0) return;
      el.animate(
        [{ transform: `translateY(${delta}px)` }, { transform: 'translateY(0)' }],
        { duration: 200, easing: 'ease' }
      );
    });
  }, [deps]);

  return { containerRef, capture };
}

const swap = <T,>(arr: T[], a: number, b: number): T[] => {
  if (b < 0 || b >= arr.length) return arr;
  const next = [...arr];
  [next[a], next[b]] = [next[b], next[a]];
  return next;
};

function SortableList() {
  const [items, setItems] = useState([
    { id: '1', name: '재질' },
    { id: '2', name: '치아위치' },
    { id: '3', name: '제품군' },
  ]);
  const { containerRef, capture } = useFlipList(items);

  const move = (index: number, dir: -1 | 1) => {
    capture();
    setItems((prev) => swap(prev, index, index + dir));
  };

  return (
    <ul ref={(el) => (containerRef.current = el)}>
      {items.map((item, i) => (
        <li key={item.id} data-row-id={item.id}>
          {item.name}
          <button onClick={() => move(i, -1)}>↑</button>
          <button onClick={() => move(i, 1)}>↓</button>
        </li>
      ))}
    </ul>
  );
}
profile
React, React-Native https://darcyu83.netlify.app/

0개의 댓글