はじめに
こちらの記事で作成した改良アプリとなります。
改良していく中で時間のかかった箇所などをまとめて記載しようと思います。
改修した箇所
- 登録する部分はDialogを開いて、登録するようにしました
- 登録するとすぐに画面上でも反映するようにしました
時間がかかった箇所、つまずいた箇所
- 新規でデータ登録したさいに、そのまま画面上でデータ更新がされなかったこと
画面上で登録ボタン押したにも関わらず、画面が更新されていない状態がありました。
なぜ画面が更新されていないのか疑問に思いました。
そのさい、
DBを登録した、更新した = 画面更新した と誤って認識していたことに気づきました。
2. mockの共有化することのデメリット
詳細を以下でも記載しています。
mockを共有することで、そのデータを使った前のテスト結果に左右されてしまうことがあることを学びました。
3. react-hook-formの使い方
基本的な記載方法はもちろん、onClickとreact-hook-formの併用はできないなどの制約を学びました。
type FormValues = {
studyContent: string;
studyTime: string;
}
const { register, handleSubmit, formState: { errors }, reset } = useForm<FormValues>();
4. どの部分をコンポーネント化するか
今回は画面とDialogで切り分けて作成しました。
理由は、機能ごとでわかれていたからです。
しかし、もっと切り分けたほうがいいとかあるかもしれないです。
5. 編集ボタンを押下したさいに、登録済みデータを表示させる方法に苦戦した
stateに情報を持たして、条件分岐で編集用の表示にすればよいだけなのですが。
App.tsx
import { useCallback, useEffect, useState } from 'react'
import { Button, Stack, Table } from '@chakra-ui/react'
import { GetAllRecords } from './lib/GetTableLib';
import { StudyRecord } from "./domain/GetTableDomain";
import {Dialogs} from "./components/organism/Dialog"
import { supabase } from './utils/supabase';
function App() {
const [loading, setLoading] = useState(true);
const [studyRecords, setStudyRecord] = useState<StudyRecord[]>([]);
const [editingRecord, setEditingRecord] = useState<StudyRecord | null>(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const handleAdd = async () => {
const data = await GetAllRecords();
setStudyRecord(data);
setEditingRecord(null);
}
const onUpdateClick = useCallback(async (id: string) => {
const selectData = await supabase.from("study-record").select('*').eq('id', id).single();
if (selectData.error) {
throw new Error(selectData.error.message);
}
setEditingRecord(selectData.data);
setIsDialogOpen(true);
},[])
const onDeleteClick = useCallback(async (id: string) => {
const deleteData = await supabase.from("study-record").delete().eq('id', id)
if (deleteData.error) {
throw new Error(deleteData.error.message);
}
const data = await GetAllRecords();
setStudyRecord(data);
}, [])
useEffect(() => {
const getAllRecords = async () => {
const data = await GetAllRecords();
setStudyRecord(data);
setLoading(false);
}
getAllRecords();
}, [])
if (loading) {
return <p data-testid="loading">Loading</p>
}
return (
<>
<h1 data-testid="title">学習記録一覧</h1>
<Button variant="outline" width="100px" data-testid="addbutton" onClick={() => {
setEditingRecord(null);
setIsDialogOpen(true);
}}>登録</Button>
<Dialogs onAdd={handleAdd} open={isDialogOpen} setOpen={setIsDialogOpen} initialData={editingRecord} />
<Stack gap="10" data-testid="table">
<Table.Root size="sm">
<Table.Header>
<Table.Row>
<Table.ColumnHeader>学習内容</Table.ColumnHeader>
<Table.ColumnHeader>学習時間</Table.ColumnHeader>
</Table.Row>
</Table.Header>
<Table.Body data-testid="tablelist">
{studyRecords.map((studyRecord) => (
<Table.Row key={studyRecord.id} data-testid="data-item">
<Table.Cell>{studyRecord.studyContent}</Table.Cell>
<Table.Cell>{studyRecord.studyTime}</Table.Cell>
<Table.Cell>
<Button onClick={() => onUpdateClick(studyRecord.id)} data-testid="updatebutton">編集</Button>
</Table.Cell>
<Table.Cell>
<Button onClick={() => onDeleteClick(studyRecord.id)} data-testid="deletebutton">削除</Button>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
</Stack>
</>
)
}
export default App
Dialog.tsx
import { FC, memo, useCallback, useState, useEffect } from "react";
import { Button, Input, Group, Dialog, Portal, CloseButton, useDisclosure } from '@chakra-ui/react';
import { useForm } from "react-hook-form";
import { supabase } from "../../utils/supabase";
import { StudyRecord } from "../../domain/GetTableDomain";
type Props = {
onAdd: () => void;
open: boolean;
setOpen: (open: boolean) => void;
initialData: StudyRecord | null;
}
type FormValues = {
studyContent: string;
studyTime: string;
}
export const Dialogs: FC<Props> = memo((prop) => {
const { onAdd, open, setOpen, initialData } = prop
const { register, handleSubmit, formState: { errors }, reset } = useForm<FormValues>();
const onOpenChange = (event: { open: boolean }) => {
setOpen(event.open);
}
const onSubmit = useCallback(async (data: FormValues) => {
if (initialData) {
const updateData = await supabase.from("study-record").update({studyContent: data.studyContent, studyTime: data.studyTime ? Number(data.studyTime) : null}).eq('id', initialData.id);
if (updateData.error) {
throw new Error(updateData.error.message);
}
} else {
const registerData = await supabase.from("study-record").insert([{studyContent: data.studyContent, studyTime: data.studyTime ? Number(data.studyTime) :null}]);
if (registerData.error) {
throw new Error(registerData.error.message);
}
}
onAdd()
setOpen(false);
reset();
}, [onAdd, setOpen, initialData, reset])
useEffect(() => {
if (initialData) {
reset({
studyContent: initialData.studyContent,
studyTime: initialData.studyTime,
});
} else {
reset({
studyContent: "",
studyTime: "0",
})
}
}, [initialData, reset]);
return (
<Dialog.Root open={open} onOpenChange={onOpenChange}>
<Portal>
<Dialog.Backdrop />
<Dialog.Positioner {...({} as any)}>
<Dialog.Content as="form" onSubmit={handleSubmit(onSubmit)} {...({} as any)}>
<Dialog.Header>
<Dialog.Title data-testid="add-title" {...({} as any)}>{initialData ? "記録編集": "新規学習記録"}</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Group attached w="fall" maxW="lg">
<Input type="text" {...register("studyContent", {required:"内容の入力は必須です",})} data-testid="content-input"/>
{errors.studyContent && <p style={{color : "red"}} data-testid="studycontent-error">{errors.studyContent.message}</p>}
<Input type="number" {...register("studyTime", {required: "時間の入力は必須です", min: {value: 0, message: "時間は0以上である必要があります"}})} data-testid="time-input"/>
{errors.studyTime && <p style={{color : "red"}} data-testid="studytime-error">{errors.studyTime.message}</p>}
</Group>
</Dialog.Body>
<Dialog.Footer>
<Button colorScheme="teal" width="100px" type="submit">登録</Button>
<Dialog.ActionTrigger asChild>
<Button colorScheme="teal" width="100px">キャンセル</Button>
</Dialog.ActionTrigger>
</Dialog.Footer>
<Dialog.CloseTrigger asChild {...({} as any)}>
<CloseButton size="sm" />
</Dialog.CloseTrigger>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</Dialog.Root>
)
})
GetTableDomain.ts
export class StudyRecord {
constructor(
public id: string,
public studyContent: string,
public studyTime: string,
) { }
public static newStudyRecord(
id: string,
studyContent: string,
studyTime: string,
): StudyRecord {
return new StudyRecord(
id,
studyContent,
studyTime,
)
}
}
おわりに
まだまだスキルも身にみにつけていきます
参考文献

