Chakra v2からChakra v3へ極力手を抜きながら移行する方法

## 結論

Wrapperをつくる

## まえがき

ついにChakra v3が来ましたね。framer-motionの依存がなくなり、軽量かつ高速になったようです。しかし、当然ながら破壊的変更が多く、移行のコストが大きいと感じました。そこで、今回は移行を進めていく際の方針を共有します。

## V2Modal

```tsx:TestModal.tsx
import { useDisclosure } from "@chakra-ui/react";
import {
  Button,
  Modal,
  ModalBody,
  ModalCloseButton,
  ModalContent,
  ModalFooter,
  ModalHeader,
  ModalOverlay,
  useDisclosure,
} from "@chakra-ui/react";
export const TestModal = () => {
  const { isOpen, onOpen, onClose } = useDisclosure();
  return (
    <>
      <Button onClick={onOpen} size={"sm"}　closeOnEsc={true}>
        Open
      </Button>

      <Modal isOpen={isOpen} onClose={onClose}>
        <ModalOverlay />
        <ModalContent>
          <ModalHeader>header</ModalHeader>
          <ModalCloseButton />
          <ModalBody>body</ModalBody>
          <ModalFooter>
            <Button colorScheme="blue" mr={3} onClick={onClose}>
              Close
            </Button>
            <Button variant="ghost">Secondary Action</Button>
          </ModalFooter>
        </ModalContent>
      </Modal>
    </>
  );
};
```

超基本的なモーダルですね。V3ではModalがなくなりDialogになっています。いろいろ変わってしまっており、このままでは動きません。

## Wrapperを用意する

```tsx:Modal.tsx
import {
  DialogBackdrop,
  DialogBody,
  DialogCloseTrigger,
  DialogContent,
  DialogFooter,
  DialogHeader,
  DialogRoot,
} from "@/components/ui/dialog";

type ModalProps = {
  children: React.ReactNode;
  isOpen: boolean;
  onClose: () => void;
  closeOnEsc?: boolean;
};
export const Modal = ({
  children,
  isOpen,
  onClose,
  closeOnEsc,
}: ModalProps) => {
  const onOpenClose = () => {
    if (isOpen) {
      onClose();
    }
  };
  return (
    <DialogRoot
      open={isOpen}
      onOpenChange={onOpenClose}
      closeOnEscape={closeOnEsc}
    >
      {children}
    </DialogRoot>
  );
};

export const ModalBody = DialogBody;
export const ModalCloseButton = DialogCloseTrigger;
export const ModalOverlay = DialogBackdrop;
export const ModalHeader = DialogHeader;
export const ModalFooter = DialogFooter;
export const ModalContent = DialogContent;

```

こういうwrapperを用意すれば、呼び出し元はimport先を変更するだけで大丈夫です。

## WrapperからModalを呼び出す

```tsx:TestModal.tsx
import { Button, useDisclosure } from "@chakra-ui/react";
import {
  Modal,
  ModalBody,
  ModalCloseButton,
  ModalContent,
  ModalFooter,
  ModalHeader,
  ModalOverlay,
} from "../components/v2/Modal";
export const TestModal = () => {
  const { open: isOpen, onOpen, onClose } = useDisclosure();
  return (
    <>
      <Button onClick={onOpen} size={"sm"}>
        Open
      </Button>

      <Modal isOpen={isOpen} onClose={onClose} closeOnEsc={true}>
        <ModalOverlay />
        <ModalContent>
          <ModalHeader>header</ModalHeader>
          <ModalCloseButton />
          <ModalBody>body</ModalBody>
          <ModalFooter>
            <Button colorScheme="blue" mr={3} onClick={onClose}>
              Close
            </Button>
            <Button variant="ghost">Secondary Action</Button>
          </ModalFooter>
        </ModalContent>
      </Modal>
    </>
  );
};

```

import先が変わっただけで、他の部分は何も変えなくて良いですね。useDisclosureがisOpen -> openに変更されていたり、colorSchemeがcolorPaletteになっていたり、isDisabledがdisabledになっていたり、closeOnEscがcloseOnEscapeになっていたりと細かい変更点があります。こういう部分も含めたWrapperを書いてあげると良いでしょう。

## というかV2とV3を混在させたい場合

あんまりおすすめしませんが（バンドルサイズもやばいことになるし）、やるなら使うコンポーネントだけをV2からインポートしてエクスポートするだけのWrapperライブラリを作成します。V2のコンポーネントを使うときだけは、そのWrapperライブラリからインポートするようにすればV2とV3が共存できます。やらんほうが良いと思うけど一応。

新しいコンポーネントを書くときにV3を利用しつつ、いままで書いてきたコンポーネントたちはWrapperでとりあえず動く状態にする、というのが一番手軽だと思います。でもcolorPaletteが変わっていたり、inputのデザインが変わっていたりと非常に破壊的な変更があるため、全然違うデザインになってしまうなぁ、という問題があります。いまのプロジェクトはV2系でいって、次のプロジェクトからV3にするとかの選択肢もあるでしょうね。そのほうが楽かな……。

chakra v3にしてみたら700個くらいtypeエラーが出て諦めそうになりましたが、Wrapperを書く方針にしてからは2時間くらいでなんとかなりました。デザイン変わりすぎたので、そのブランチはお蔵入りになりましたが……。