Open6

Update chakra-ui v2 to v3 to support React19, Next15

KiKiKi KiKiKiKiKi KiKi

React 19, Next 15 にアップデートしたら Chakura-ui v2 の page.tsx で呼び出している <ChakraProvider> の部分で延々とエラーが発生したので Chakura-ui を v3 にアップデートする

page.tsx
import { Provider } from './Provider.tsx';

export default async function Page() {
  return (<Provider><PageRoot /></Provider>);
}
./Provider.tsx
import { FC, ReactNode } from "react";
import { ChakraProvider } from "@chakra-ui/react";
type ProvidersProps = {
  children: ReactNode;
};
export const Provider: FC<Readonly<ProvidersProps>> = ({ children }) => (
  <ChakraProvider>{children}</ChakraProvider>
);

Page に "use client" をつけて client component にしても変わらず

KiKiKi KiKiKiKiKi KiKi

環境

npm list

  • react 19.0.0
  • react-dom 19.0.0
  • next 15.1.2
  • typescript 5.7.2

Chakra-ui を v3 にアップデートする

https://www.chakra-ui.com/

@chakra-ui/icons     ^2.1.1  →    ^2.2.4
@chakra-ui/next-js  ^2.2.0  →    ^2.4.2
@chakra-ui/react     ^2.8.2  →    ^3.3.0

上記のようにパッケージをアップデートした

Next.js の場合 ChakraProvider は クライアントコンポーネントでないとエラーになる

先のエラーは <ChakraProvider> がクライアントコンポーネントでなかったことで発生していたっぽい

./Provider.tsx
+ "use client";
import { FC, ReactNode } from "react";
import { ChakraProvider } from "@chakra-ui/react";
type ProvidersProps = {
  children: ReactNode;
};
export const Provider: FC<Readonly<ProvidersProps>> = ({ children }) => (
  <ChakraProvider>{children}</ChakraProvider>
);

ChakraProvider に value が必須になっていたので修正する

Property 'value' is missing in type '{ children: ReactNode; }' but required in type 'ChakraProviderProps'.ts(2741)

./Provider.tsx
- import { ChakraProvider } from "@chakra-ui/react";
+ import { ChakraProvider, defaultSystem } from "@chakra-ui/react";
type ProvidersProps = {
  children: ReactNode;
};
export const Providers: FC<Readonly<ProvidersProps>> = ({ children }) => (
-   <ChakraProvider>{children}</ChakraProvider>
+   <ChakraProvider value={defaultSystem}>{children}</ChakraProvider>
);

cf. https://github.com/chakra-ui/chakra-ui/blob/main/sandbox/next-app/app/provider.tsx

next.config.mjs に設定を追加する

Optimize Bundle
We recommend using the experimental.optimizePackageImports feature in Next.js to optimize your > bundle size by loading only the modules that you are actually using.

next.config.mjs
export default {
  experimental: {
    optimizePackageImports: ["@chakra-ui/react"],
  },
}

https://www.chakra-ui.com/docs/get-started/frameworks/next-app

KiKiKi KiKiKiKiKi KiKi

Spinner を使っている箇所でエラーが発生する

Loader.tsx
import { FC } from "react";
import { Box, BoxProps, Spinner, SpinnerProps } from "@chakra-ui/react";
type LoaderProps = Pick<SpinnerProps, "size" | "color"> & BoxProps;
export const Loader: FC<LoaderProps> = ({
  size = "md",
  color = "blue.400",
  ...boxProps
}) => (
  <Box {...boxProps}>
    <Spinner size={size} color={color} />
  </Box>
);

./node_modules/@chakra-ui/icons/dist/esm/Spinner.mjs
Attempted import error: 'forwardRef' is not exported from '@chakra-ui/react' (imported as 'forwardRef').

@chakra-ui/icons が対応しきれてないっぽい

Both @chakra-ui/icons and forwardRef
have been removed.

  • Replace forwardRef with the react one
  • Replace @chakra-ui/icons with a robust library like react-icons

cf. https://github.com/chakra-ui/chakra-ui/issues/8986#issuecomment-2435935656

cf

KiKiKi KiKiKiKiKi KiKi

@chakra-ui/icons を削除する

$ npm uninstall @chakra-ui/icons

react-icons などから別のアイコンライブラリを選定して置き換える

https://react-icons.github.io/react-icons/

IconButton の使い方が変わっているので対応する

  1. icon={<Icon />} プロパティが廃止されている
  2. variant プロパティの link がなくなっている

https://www.chakra-ui.com/docs/components/icon-button

import { IconButton } from "@chakra-ui/react";

const CloseButton = ({ onClose }) => {
  return (
    <IconButton
        onClick={onClose}
        aria-label="close timeline"
-       icon={<CloseIcon />}
        size="sm"
-       variant="link"
+       variant="plain"
-   />
+   >
+     <CloseIcon />
+   </IconButton>
  );
}
KiKiKi KiKiKiKiKi KiKi

<Checkbox> が使えなくなってる

Checkbox コンポーネントを使っている箇所でクラッシュする

https://www.chakra-ui.com/docs/components/checkbox

import { Checkbox } from "@/components/ui/checkbox"

@/components/ui/checkbox からインポートしろとある

事前に snippet を生成する必要があった

cf. https://www.chakra-ui.com/docs/get-started/installation#add-snippets

$ npx @chakra-ui/cli snippet add

上記コマンドでスニペットを生成し、それを使う方針になっている
デフォルトでは @/components/ui ディレクトリに生成せされる

https://qiita.com/kkawakami/items/754ab770cf74b3815fa3

別のディレクトリに snippet を生成する

すでに @/components/ui ディレクトリを使用していると困る
--outdir オプションを使うことで別のディレクトリに snippet を生成することができた

% npx @chakra-ui/cli snippet add --outdir ./src/components/chakraui

今回は @/components/chakraui で呼び出せるようにした

https://www.chakra-ui.com/guides/snippet-specify-custom-directory

snippet を生成すれば import 元を変更するだけで良い

- import { Checkbox } from "@@chakra-ui/react";
+ import { Checkbox } from "@/components/chakraui/checkbox";
KiKiKi KiKiKiKiKi KiKi

v3 では Checkbox onChange イベントの currentTarge が label になる

evt.currentTarget.valueundefined になっていた

MyCheckbox.tsx
import { FC, ReactNode } from "react";
import { Checkbox } from "@/components/chakraui/checkbox";
import { useDisplayInfoMutators } from './hooks/useDisplayInfo';

type MyCheckboxProps = {
  id: string;
  isChecked?: boolean;
  label: ReactNode;
};

export const MyCheckbox: FC = ({ id, isChecked = false, label }) => {
  consy { onChange } = useDisplayInfoMutators();
  const handleToggle = (evt: ChangeEvent<HTMLInputElement>) => {
    const id = evt.currentTarget.value; // -> undefined
    const checked = evt.currentTarget.checked; // -> undefined
    onChange(id, checked);
  };
  return (
    <Checkbox
      onChange={handleToggle}
      value={id}
      checked={isChecked}
    >
      {label}
    </Checkbox>
  );
}

Chakra UI v3 では event.currentTarget<label> になるように変わっていた
event.target にすれば <input> が取得できる

MyCheckbox.tsx
export const MyCheckbox: FC = ({ id, isChecked = false, label }) => {
  consy { onChange } = useDisplayInfoMutators();
  const handleToggle = (evt: ChangeEvent<HTMLInputElement>) => {
-   const id = evt.currentTarget.value; // -> undefined
+   const id = evt.target.value;
-   const checked = evt.currentTarget.checked; // -> undefined
+   const checked = evt.target.checked;
    onChange(id, checked);
  };
  return (
    <Checkbox
      onChange={handleToggle}
      value={id}
      checked={isChecked}
    >
      {label}
    </Checkbox>
  );
}

単純に check されているかどうかなら onCheckedChange を使えばOK

MyCheckbox.tsx
export const MyCheckbox: FC = ({ id, isChecked = false, label }) => {
  consy { onChange } = useDisplayInfoMutators();
  const handleToggle = (checked: boolean) => {
    onChange(id, checked);
  };
  return (
    <Checkbox
      onCheckedChange={handleToggle}
      value={id}
      checked={isChecked}
    >
      {label}
    </Checkbox>
  );
}

https://www.chakra-ui.com/docs/components/checkbox