1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Chakra V3】<button> cannot be a descendant of <button>エラー

Posted at

はじめに

ChakraのV3の勉強をしている中でタイトルのエラーが発生し、こういった仕様を理解できていなかったため備忘として残します。

問題

ChakraのDialogを使い以下のようなモーダルを作成しています。
モーダルを開くボタンを押すと<button> cannot be a descendant of <button>エラーが出てきます。

レコーディング 2026-01-16 202646.gif

以下ソースコード全文です。

ソースコード全文 ```tsx
import PrimaryButton from "@/atoms/button/PrimaryButton";
import { DialogContent, DialogRoot, DialogTitle } from "@/components/ui/dialog";
import { Field } from "@/components/ui/field";
import {
  Button,
  CloseButton,
  Dialog,
  Flex,
  Input,
  Stack,
  Text,
  type UseDisclosureReturn,
} from "@chakra-ui/react";
import { memo, type FC } from "react";
import type { FieldErrors, UseFormRegister } from "react-hook-form";

interface FormValues {
  studyContent: string;
  studyTime: number;
}

type Props = Pick<UseDisclosureReturn, "open" | "onToggle"> & {
  onSubmit: (
    e?: React.BaseSyntheticEvent<object, any, any> | undefined
  ) => Promise<void>;
  errors: FieldErrors<FormValues>;
  register: UseFormRegister<FormValues>;
  isValid: boolean;
};

const InputModal: FC<Props> = ({
  open,
  onToggle,
  onSubmit,
  errors,
  register,
  isValid,
}) => {
  return (
    <DialogRoot size={"sm"} open={open} onOpenChange={onToggle}>
      <DialogContent>
        <Dialog.Header>
          <DialogTitle>学習記録登録</DialogTitle>
        </Dialog.Header>
        <Dialog.Body>
          <Stack>
            <form id="register" onSubmit={onSubmit}>
              <Stack gap="4" align="flex-start" maxW="md">
                <Field
                  label={"学習記録"}
                  errorText={errors.studyContent?.message}
                  invalid={!!errors.studyContent}
                >
                  <Input
                    {...register("studyContent", {
                      required: "内容の入力は必須です",
                    })}
                  />
                </Field>

                <Field
                  label={"学習時間"}
                  errorText={errors.studyTime?.message}
                  invalid={!!errors.studyTime}
                >
                  <Flex align={"center"} w="100%" gap={2}>
                    <Input
                      type="number"
                      min={0}
                      w={"90%"}
                      {...register("studyTime", {
                        pattern: {
                          value: /^(0|[1-9]\d*)(\.\d+)?$/,
                          message: "整数で入力してください。",
                        },
                        required: "時間の入力は必須です",
                        min: {
                          value: 0,
                          message: "時間は0以上である必要があります",
                        },
                      })}
                    />
                    <Text>時間</Text>
                  </Flex>
                </Field>
              </Stack>
            </form>
          </Stack>
        </Dialog.Body>
        <Dialog.Footer>
          <PrimaryButton type="submit" form="register" isDisabled={!isValid}>
            登録
          </PrimaryButton>
          <Dialog.ActionTrigger asChild>
            <Button>キャンセル</Button>
          </Dialog.ActionTrigger>
        </Dialog.Footer>

        <Dialog.CloseTrigger>
          <CloseButton size="sm" />
        </Dialog.CloseTrigger>

      </DialogContent>
    </DialogRoot>
  );
};

export default memo(InputModal);

解決方法

Dialog.CloseTriggerコンポーネントにasChildプロパティを追加すればOKです。

修正前
<Dialog.CloseTrigger>
  <CloseButton size="sm" />
</Dialog.CloseTrigger>
修正後
- <Dialog.CloseTrigger>
+ <Dialog.CloseTrigger asChild>
  <CloseButton size="sm" />
</Dialog.CloseTrigger>

本エラーの原因

今回の原因はbutton要素がネストしていたことです。
asChildプロパティを付与することでなぜ解決するのか、次の章で解説します。。

asChildプロパティとは

親コンポーネントの機能(アクセシビリティ属性、イベントハンドラなど)を、子要素に引き継がせる仕組みです。
以下記事にasChildプロパティ詳細やリスクがわかりやすく説明されています。
https://zenn.dev/tsuboi/articles/8abddb1ae3038f#aschild-%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B

asChildプロパティが付与された親コンポーネント(今回の場合は<Dialog.CloseTrigger>)は、HTMLに存在しなくなります。

実際にasChildプロパティのあるなしでどのようにHTMLが作成されるかサンプルをお見せします。

asChild無しの場合

まずはわかりやすいようにasChildプロパティが無い場合。

asChild無し(tsx)
<Dialog.CloseTrigger>
  <button id="sample">★★★</button>
</Dialog.CloseTrigger>

↓↓↓

asChild無し(HTML)
<button data-scope="dialog" data-part="close-trigger">
  <button id="sample">★★★</button>
</button>

親と子の両方のボタンが表示されています。

今回のエラー原因であるbutton要素のネストは、まさにこの両方のボタンが表示されたため発生しました。


asChildありの場合

asChildあり(tsx)
<Dialog.CloseTrigger asChild>
  <button id="sample">★★★</button>
</Dialog.CloseTrigger>

↓↓↓

asChildあり(HTML)
<button id="sample" data-scope="dialog" data-part="close-trigger">
  ★★★
</button>

ボタンは1つだけ表示されています。つまり、親は自分が持っている属性情報をすべて子供に引き継がせ、自分自身は全くいなくなりました。
ボタンはネストしなくなったので万々歳です。

おわりに

知っていれば瞬殺で解決できるけど、知らないと原因の特定ですら時間がかかりますね…。
ボタンがネストしてエラーにあることはasChildの存在など、まだまだ学習中です...!

参考

JISOUのメンバー募集中!

プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!
▼▼▼
https://projisou.jp

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?