[React-Beautiful-Dnd] Issue with React 18

Tags
React
Created
June 12, 2024

Background

KanbanWave 프로젝트 개발 중 드래그 앤 드롭(이하 DnD) 기능 구현을 위해 react-beautiful-dnd 라이브러리를 사용했다. 사용 중에 아이템을 DnD 했을 때, 아래 사진과 같은 에러가 발생했다.

image

이 에러 메시지는 특정 droppable 요소를 찾지 못할 때 발생한다.

검색해보니 react-beautiful-dnd 라이브러리가 아직 React 18의 StrictMode를 지원하지 않아서 생기는 문제라고 한다.

Cause

Github issue 중에 아래와 같은 글을 발견했다.

image

react-beautiful-dnduseLayoutEffect을 사용하여 DnD 기능에 필요한 droppable 요소를 등록한다. 아마 이 과정에서 에러가 발생한 것이 아닐까 생각해본다.

  • 컴포넌트가 처음 마운트될 때, useLayoutEffect가 실행되어 droppable 요소를 등록한다.
  • StrictMode가 활성화된 경우, React 컴포넌트를 재마운트한다.
  • 컴포넌트가 다시 마운트될 때 useLayoutEffect가 다시 실행되어야 하지만, 의존성 배열이 변경되지 않았다면 useLayoutEffect가 실행되지 않을 수 있다.
  • 이로 인해서 droppable 요소가 제대로 등록되지 않아, react-beautiful-dnd가 해당 요소를 찾지 못하고 에러를 발생시킨다.

결과적으로, react-beautiful-dnd가 droppable 요소를 관리하는 방식이 기대한대로 동작하지 않았거나, 타이밍 문제 때문에 droppable 요소가 등록되지 않는 것이다.

Solutions

몇 가지 해결 방법들이 논의되고 있다.

1. React.StrictMode 제거

전체 App 컴포넌트를 감싸고 있는 React.StrictMode를 지우면 된다. 그런데 단지 이 라이브러리를 사용하기 위해 지우는 것이 해결책인지 잘 모르겠다.

StrictMode에 대해서는 여기에서 자세히 다루었다.

2. Custom Droppable 컴포넌트 사용

import { useEffect, useState } from "react";
import { Droppable, DroppableProps } from "react-beautiful-dnd";

export const StrictModeDroppable = ({ children, ...props }: DroppableProps) => {
  const [enabled, setEnabled] = useState(false);

  useEffect(() => {
    const animation = requestAnimationFrame(() => setEnabled(true));

    return () => {
      cancelAnimationFrame(animation);
      setEnabled(false);
    };
  }, []);

  if (!enabled) {
    return null;
  }

  return <Droppable {...props}>{children}</Droppable>;
};
  • 상태 관리: enabled라는 상태 변수를 사용하여 Droppable 컴포넌트의 렌더링을 제어한다.
  • useEffect 훅: 컴폰너트가 마운트딜 때 requestAnimationFrame을 사용하여 setEnabled(true)를 호출하고, 컴포넌트가 언마운트될 때 cancelAnimationFrame을 호출하여 상태를 초기화한다.
  • 조건부 렌더링: enabledtrue일 때만 Droppable 컴포넌트를 렌더링한다. 이렇게 함으로써 컴포넌트가 첫 번째 렌더링 사이클 동안 마운트되지 않고, 다음 애니메이션 프레임 사이클에서 마운트된다.

먼저, 왜 이 방법으로 문제를 해결할 수 있는지를 알아보자.

requestAnimationFrame을 사용하는 이유

requestAnimationFrame은 브라우저의 다음 페인트(화면 갱신) 사이클이 시작되기 전에 콜백 함수가 실행되도록 예약할 수 있다. 이를 통해 컴포넌트가 완전히 마운트 된 후에 특정 작업을 수행할 수 있다. 즉, react-beautiful-dnd가 드롭 가능한 항목을 등록하는 데 필요한 시간을 확보할 수 있는 것이다. 아! requestAnimationFrame에 대한 글은 여기에 자세히 정리되어 있다.

3. React 18을 지원하는 @hello-pangea/dnd로 대체

(이 방법은 사실 2번을 적용한 후 알게 되었다.)

Atlassian가 더이상 react-beautiful-dnd 라이브러리를 지원하지 않는데, 고맙게도 대체할 수 있는 라이브러리가 나왔다. 오픈소스를 포크해서 새로 만든 hello-pangea/dnd라는 라이브러리다.

가장 큰 변화는 React 18 지원, StrictMode 지원. 또한, 문법이 react-beautiful-dnd와 같다고 한다. 이 밖에 자세한 내용은 Github issue에 나와있다.