はじめに
ChakraのV3の勉強をしている中でタイトルのエラーが発生し、こういった仕様を理解できていなかったため備忘として残します。
問題
ChakraのDialogを使い以下のようなモーダルを作成しています。
モーダルを開くボタンを押すと<button> cannot be a descendant of <button>エラーが出てきます。
以下ソースコード全文です。
ソースコード全文
```tsximport 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プロパティが無い場合。
<Dialog.CloseTrigger>
<button id="sample">★★★</button>
</Dialog.CloseTrigger>
↓↓↓
<button data-scope="dialog" data-part="close-trigger">
<button id="sample">★★★</button>
</button>
親と子の両方のボタンが表示されています。
今回のエラー原因であるbutton要素のネストは、まさにこの両方のボタンが表示されたため発生しました。
asChildありの場合
<Dialog.CloseTrigger asChild>
<button id="sample">★★★</button>
</Dialog.CloseTrigger>
↓↓↓
<button id="sample" data-scope="dialog" data-part="close-trigger">
★★★
</button>
ボタンは1つだけ表示されています。つまり、親は自分が持っている属性情報をすべて子供に引き継がせ、自分自身は全くいなくなりました。
ボタンはネストしなくなったので万々歳です。
おわりに
知っていれば瞬殺で解決できるけど、知らないと原因の特定ですら時間がかかりますね…。
ボタンがネストしてエラーにあることはasChildの存在など、まだまだ学習中です...!
参考
JISOUのメンバー募集中!
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!
▼▼▼
https://projisou.jp
