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?

【Vite × React × TypeScript】Chakra UI v3でユーザーカード押下でモーダルを表示する方法

Last updated at Posted at 2024-12-12

はじめに

お疲れ様です、りつです。

今日はChakra UI v3でモーダルを実装する方法について備忘録もかねてご紹介します。

問題

現在以下のようなユーザー一覧画面を実装しております。

image.png

今回実装したかった機能は、ユーザー一覧にあるカードをクリックした際に、対象のユーザー詳細モーダルを表示するといったものです。

image.png

参考にしている学習動画がChakra v1のModalコンポーネントを使用しているのですが、Chakra v3ではDialogコンポーネントに名称が変更されていました。

Modal

  • Renamed to Dialog
  • Remove isCentered prop in favor of using the placement=center prop
  • Removed isOpen and onClose props in favor of using the open and onOpenChange props

また、Chakra UIの公式ドキュメントだとDialogTriggerコンポーネントでButtonコンポーネントを囲んで、そのボタンを押下した際にモーダルを表示させるサンプルが多いです。

ただ、今回の実装で同じ記述方法をしてしまうと、モーダル表示部分だけコンポーネント化するのが難しくなってしまいそうでした。

そのため、以下のControlledのサンプルを元に、openonOpenChangeプロパティを使用して調整を行いました。

解決方法

まず一覧画面の実装が以下の通りです。

以下の処理をそれぞれコンポーネント化し、呼び出しています。

  • ユーザーカード:UserCardコンポーネント
  • モーダル:UserDetailModalコンポーネント

また、opensetOpenというモーダルの開閉管理用のステートを用意しています。
UserCardへ渡すクリックイベント(onClickUser)で、setOpen={true}を指定することでモーダルを開くことができます。

src/components/pages/UserManagement.tsx
import React, { memo, useCallback, useEffect, useState } from "react";
import { Center, Flex, HStack, Spinner } from "@chakra-ui/react";
import { UserCard } from "@/components/organisms/user/UserCard";
import { UserDetailModal } from "@/components/organisms/user/UserDetailModal";
import { useAllUsers } from "@/hooks/useAllUsers";
import { useSelectUser } from "@/hooks/useSelectUser";

export const UserManagement: React.FC = memo(() => {
  const [open, setOpen] = useState(false);
  const { getUsers, loading, users } = useAllUsers();
  const { onSelectUser, selectedUser } = useSelectUser();

  useEffect(() => getUsers(), []);

  const onClickUser = useCallback((id: number) => {
    onSelectUser({ id, users });
    setOpen(true);
  }, [users, onSelectUser]);

  return (
    <>
      { loading ? (
        <Center h="100vh">
          <Spinner />
        </Center>
      ) : (
        <HStack wrap="wrap" p={{ base: 4, md: 10 }}>
          { users.map((user) => (
            <Flex align='flex-start' key={user.id} mx="auto">
              <UserCard
                id={user.id}
                imageUrl="https://picsum.photos/200"
                userName={user.username}
                fullName={user.name}
                onClick={onClickUser}
              />
            </Flex>
          )) }
        </HStack>
      ) }
      <UserDetailModal user={selectedUser} open={open} setOpen={setOpen} />
    </>
  );
});

以下が、UserCardコンポーネントの内容です。

src/components/organisms/user/UserCard.tsx
import React, { memo } from "react";
import { Box, Image, Stack, Text } from "@chakra-ui/react";

type Props = {
  id: number;
  imageUrl: string;
  userName: string;
  fullName: string;
  onClick: (id: number) => void;
};

export const UserCard: React.FC<Props> = memo((props) => {
  const { id, imageUrl, userName, fullName, onClick } = props;
  return (
    <Box
      w="260px"
      h="260px"
      bg="white"
      borderRadius="10px"
      shadow="md"
      p={4}
      _hover={{ cursor: "pointer", opacity: 0.8 }}
      onClick={() => onClick(id)}
    >
      <Stack textAlign="center">
        <Image
          borderRadius="full"
          boxSize="160px"
          src={imageUrl}
          alt={userName}
          m="auto" />
        <Text fontSize="lg" fontWeight="bold">
          {userName}
        </Text>
        <Text fontSize="sm" color="gray">
          {fullName}
        </Text>
      </Stack>
    </Box>
  );
});

最後に、モーダル表示部分をコンポーネント化したのが以下のUserDetailModalコンポーネントです。

ユーザー一覧画面(UserManagement.tsx)で渡されたモーダル開閉管理用のステートを受け取り、それをもとにDialogRootコンポーネントのopenonOpenChangeプロパティを指定しています。

src/components/organisms/user/UserDetailModal.tsx
import React, { memo } from "react";
import { Input, Stack } from "@chakra-ui/react";
import {
  DialogBody,
  DialogCloseTrigger,
  DialogContent,
  DialogHeader,
  DialogRoot,
  DialogTitle,
} from "@/components/ui/dialog"
import { Field } from "@/components/ui/field";
import { User } from "@/types/api/user";

type Props = {
  user: User | null;
  open: boolean;
  setOpen: React.Dispatch<React.SetStateAction<boolean>>;
};

export const UserDetailModal: React.FC<Props> = memo((props) => {
  const { user, open, setOpen } = props;
  return (
    <DialogRoot lazyMount
      open={open}
      onOpenChange={(e) => setOpen(e.open)}
      motionPreset="slide-in-bottom"
      trapFocus={false}
    >
      <DialogContent pb={6}>
        <DialogHeader>
          <DialogTitle>ユーザー詳細</DialogTitle>
        </DialogHeader>
        <DialogBody mx={4}>
          <Stack gap={4}>
            <Field label="名前">
              <Input value={user?.username} readOnly />
            </Field>
            <Field label="フルネーム">
              <Input value={user?.name} readOnly />
            </Field>
            <Field label="MAIL">
              <Input value={user?.email} readOnly />
            </Field>
            <Field label="TEL">
              <Input value={user?.phone} readOnly />
            </Field>
          </Stack>
        </DialogBody>
        <DialogCloseTrigger />
      </DialogContent>
    </DialogRoot>
  );
});

おわりに

今回はChakra UI v3のモーダル表示方法についてご紹介しました。

上記ではソースコードを抜粋しておりましたが、細かい実装について参考にしたい方は以下のリポジトリもご参照ください。

参考

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?