Engineer BA-Sulto Tech Blog

カテゴリデータの管理画面(UI)を実装

カテゴリデータの管理画面(UI)を実装
2025-06-08
TypeScriptでフルスタック開発マスターへの道・家計簿アプリ開発編

解説動画

YouTubeに解説動画を公開しています。 こちらもあわせて、ぜひご覧ください。

カテゴリーページを作成する

1. タブレイアウトにカテゴリータブを追加する

タブレイアウトにカテゴリーページのタブを追加します。

<Tabs.Screen name="categories" options={{ title: "Categories", tabBarIcon: ({ color }) => ( <FontAwesome size={28} name="th-list" color={color} /> ), }} />

2. カテゴリーページを作成する

ベースとなるカテゴリーページを作成します。 (tabs)ディレクトリにcategories.tsxを作成します。

import React from "react"; import { Text, View } from "react-native"; export default function Tab() { return ( <View> <Text>categories</Text> </View> ); };

3. カテゴリーページのヘッダーを作成する

ヘッダーを作成します。 Gluestack UIのHeadingコンポーネントとFabコンポーネントとIconコンポーネントをインストールします。

https://gluestack.io/ui/docs/components/heading https://gluestack.io/ui/docs/components/fab https://gluestack.io/ui/docs/components/icon

npx gluestack-ui add heading fab icon

CategoryHeaderコンポーネントを作成します。 (tabs)ディレクトリにcategory-header.tsxを作成します。

import React from "react"; import { Text, View } from "react-native"; export default function CategoryHeader() { return ( <View> <Text>CategoryHeader</Text> </View> ); }

カテゴリーページに、空のCategoryHeaderコンポーネントを設置します。

- import { Text, View } from "react-native"; + import { Box } from "@/components/ui/box"; + import { CategoryHeader } from "@/components/category-header"; import React from "react"; export default function Tab() { return ( - <View> - <Text>categories</Text> - </View> + <Box className="flex-1 bg-white"> + <CategoryHeader /> + </Box> ); };

CategoryHeaderコンポーネントを実装します。 _componentsディレクトリにcategories.tsxを作成します。

import { Box } from "@/components/ui/box"; import { Fab, FabIcon } from "@/components/ui/fab"; import { Heading } from "@/components/ui/heading"; import { AddIcon } from "@/components/ui/icon"; import React from "react"; export default function CategoryHeader() { return ( <Box className="h-16 flex-row items-center justify-between mx-8 my-4"> <Heading size="2xl">カテゴリー</Heading> <Fab size="sm"> <FabIcon as={AddIcon} /> </Fab> </Box> ); }

Fabコンポーネントをタップできるようにします。

- <Fab size="sm"> + <Fab size="sm" onPress={() => {console.log("add")}}>

4. カテゴリーのカードを作成する

カテゴリーのカードを作成します。 空のCategoryCardコンポーネントを設置します。

import { Box } from "@/components/ui/box"; import { Fab, FabIcon } from "@/components/ui/fab"; import { Heading } from "@/components/ui/heading"; import { AddIcon } from "@/components/ui/icon"; import React from "react"; export default function Tab() { return (   <Box className="flex-1 bg-white">   <CategoryHeader /> + <Box className="flex-1 bg-gray-100"> + <CategoryCard /> + </Box> </Box> ); };

CategoryCardコンポーネントを実装します。 Gluestack UIのCardコンポーネントとButtonコンポーネントをインストールします。

https://gluestack.io/ui/docs/components/card https://gluestack.io/ui/docs/components/button

npx gluestack-ui add card button
import { Button, ButtonIcon } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { EditIcon, TrashIcon } from "@/components/ui/icon"; import React from "react"; import { Text } from "react-native"; export default function CategoryCard({ className }: { className?: string }) { return ( <Card className={`bg-white p-4 flex-row items-center rounded-lg shadow-sm ${className}`} > <Text className="text-xl font-bold mr-auto">カテゴリー</Text> <Button variant="link"> <ButtonIcon as={EditIcon} className="m-2 w-6 h-6 text-blue-500" /> </Button> <Button variant="link"> <ButtonIcon as={TrashIcon} className="m-2 w-6 h-6 text-red-500" /> </Button> </Card> ); }

Buttonコンポーネントをタップできるようにします。

<Button variant="link" + onPress={() => { + console.log("edit"); + }} > <ButtonIcon as={EditIcon} className="m-2 w-6 h-6 text-blue-500" /> </Button> <Button variant="link" + onPress={() => { + console.log("delete"); + }} > <ButtonIcon as={TrashIcon} className="m-2 w-6 h-6 text-red-500" /> </Button>

体裁を整えます。

import { Box } from "@/components/ui/box"; import React from "react"; import CategoryCard from "../_components/category-card"; import CategoryHeader from "../_components/category-header"; export default function Tab() { return ( <Box className="flex-1 bg-white"> <CategoryHeader /> <Box className="flex-1 bg-gray-100 gap-4 pt-4"> <CategoryCard className="w-11/12 mx-auto" /> </Box> </Box> ); };

5. カテゴリーの登録・編集で使うモーダルを作成する

モーダルを作成します。 Gluestack UIのModalコンポーネントとInputコンポーネントをインストールします。

https://gluestack.io/ui/docs/components/modal https://gluestack.io/ui/docs/components/input

npx gluestack-ui add modal input

category-modal.tsxCategoryModalコンポーネントを作成します。

:::details category-modal.tsx

import { Button } from "@/components/ui/button"; import { Heading } from "@/components/ui/heading"; import { Input, InputField } from "@/components/ui/input"; import { Modal, ModalBackdrop, ModalBody, ModalContent, ModalFooter, ModalHeader, } from "@/components/ui/modal"; import React from "react"; import { Text } from "react-native"; export default function CategoryModal({ isOpen, isEdit, onClose, }: { isOpen: boolean; isEdit: boolean; onClose: () => void; }) { return ( <> <Modal isOpen={isOpen} onClose={onClose}> <ModalBackdrop /> <ModalContent> <ModalHeader> <Heading size="lg">カテゴリーの{isEdit ? "編集" : "登録"}</Heading> </ModalHeader> <ModalBody> <Input> <InputField placeholder="カテゴリー名を入力してください" className="bg-gray-100" /> </Input> </ModalBody> <ModalFooter className="w-full"> <Button action="secondary" onPress={onClose}> <Text>キャンセル</Text> </Button> <Button action="positive" onPress={() => console.log("save")}> <Text className="text-white font-bold"> {isEdit ? "更新" : "登録"} </Text> </Button> </ModalFooter> </ModalContent> </Modal> </> ); };

:::

isOpenプロパティは、モーダルの表示を制御します。 booleanで、trueであれば開き、falseであれば閉じます。 isEditプロパティは、モーダルのタイトルの編集と登録をbooleanで制御します。 onCloseプロパティは、モーダルが閉じられた時に呼び出されるコールバック関数を渡します。

6. カテゴリーの削除で使うアラートダイアログを作成する

アラートダイアログを作成します。 Gluestack UIのAlertDialogコンポーネントをインストールします。

https://gluestack.io/ui/docs/components/alert-dialog

npx gluestack-ui add alert-dialog

category-alert-dialog.tsxCategoryAlertDialogコンポーネントを作成します。

:::details category-alert-dialog.tsx

import { AlertDialog, AlertDialogBackdrop, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { Heading } from "@/components/ui/heading"; import React from "react"; import { Text } from "react-native"; export default function CategoryAlertDialog({ isOpen, onClose, }: { isOpen: boolean; onClose: () => void; }) { return ( <> <AlertDialog isOpen={isOpen} onClose={onClose}> <AlertDialogBackdrop /> <AlertDialogContent> <AlertDialogHeader className="mb-4"> <Heading size="lg">カテゴリーを削除しますか?</Heading> </AlertDialogHeader> <AlertDialogFooter> <Button action="secondary" onPress={onClose}> <Text>キャンセル</Text> </Button> <Button action="negative" onPress={() => console.log("delete")}> <Text className="text-white font-bold">削除</Text> </Button> </AlertDialogFooter> </AlertDialogContent> </AlertDialog> </> ); };

:::

7. CategoryHeaderにCategoryModalを設置する

CategoryModaluseStateをインポートします。

import CategoryModal from "./category-modal"; import React, { useState } from "react";

useStateを設置します。

const [showModal, setShowModal] = useState(false);

CategoryModalFabの下に設置します。

<CategoryModal isOpen={showModal} isEdit={false} onClose={() => setShowModal(false)} />

FabコンポーネントのonPressshowModaltrueにします。

<Fab size="sm" onPress={() => { - console.log("add"); + setShowModal(true); }} >

プラスボタンを押すと、モーダルが表示されるようになりました。 モーダルにInputコンポーネントとButtonコンポーネントを設置します。 InputコンポーネントとButtonコンポーネントをインポートします。

8. CategoryCardにCategoryModalとCategoryAlertDialogを設置する

CategoryCardCategoryModalCategoryAlertDialogを設置します。

import CategoryAlertDialog from "./category-alert-dialog"; import CategoryModal from "./category-modal"; import React, { useState } from "react";

useStateを設置します。 1つ目のuseStateModalの表示の切り替えを制御します。 また、2つ目のuseStateAlertDialogの表示の切り替えを制御します。

const [showModal, setShowModal] = useState(false); const [showAlertDialog, setShowAlertDialog] = useState(false);

最初のButtonコンポーネントの下に、CategoryModalを設置します。

<CategoryModal isOpen={showModal} isEdit={true} onClose={() => setShowModal(false)} />

2番目のButtonコンポーネントの下に、CategoryAlertDialogを設置します。

<CategoryAlertDialog isOpen={showAlertDialog} onClose={() => setShowAlertDialog(false)} />

最初のButtonコンポーネントのonPressshowModaltrueにします。

- console.log("edit"); + setShowModal(true);

2番目のButtonコンポーネントのonPressshowAlertDialogtrueにします。

- console.log("delete"); + setShowAlertDialog(true);

タグ

ExpoReact NativeTypeScriptCloudflare WorkersHonoCloudflare D1NativeWind