【AWS】AWS Amplifyを使い、フロントエンドでデータの一覧表示、作成、削除、更新する
AWS

【AWS】AWS Amplifyを使い、フロントエンドでデータの一覧表示、作成、削除、更新する

作成日:2021年12月10日
更新日:2021年12月10日

前回は、AWS AppSync を、フロントエンドと統合しました。

aws-appsync-setup-client

【AWS】AWS AppSyncを、フロントエンドと統合する

今回は、フロントエンドでデータ一覧の画面表示、作成、削除、更新します。

GraphQL で取得したデータ一覧を画面に表示する

まずは、ブラウザに GraphQL で取得したデータ一覧を表示させます。

image2

データの型を作成しましょう。

tsx
type Book = {
id: string;
title: string;
author: string;
category: {
name: string;
};
isRead: boolean;
createdAt: string;
};

mapを使って、booksのデータを表示させます。

isReadtrueの場合、『済』、falseの場合、『未』を表示するようにします。

bookに、先程作成したBook型を指定しましょう。

tsx
<TableBody>
{books.map((book: Book) => (
<TableRow key={book.id}>
<TableCell>{book.id}</TableCell>
<TableCell>{book.title}</TableCell>
<TableCell>{book.author}</TableCell>
<TableCell>{book.category.name}</TableCell>
{book.isRead ? <TableCell></TableCell> : <TableCell></TableCell>}
<TableCell>{`${book.createdAt}`}</TableCell>
</TableRow>
))}
</TableBody>

ブラウザで確認すると、

image3

データを表示することができました。

console.log で確認すると、book データを全て取得しています。

image4

category の id や updatedAt などは必要ないので、取得しないようにしましょう。

src/graphql フォルダの queries.ts を開きます。

listBooksitemsの内容を必要なデータだけ取得するよう、修正しましょう。

tsx
export const listBooks = /* GraphQL */ `
query ListBooks(
$filter: ModelBookFilterInput
$limit: Int
$nextToken: String
) {
listBooks(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
title
author
category {
name
}
isRead
createdAt
}
nextToken
}
}
`;

保存して、console.log で確認すると、

image5

必要なデータのみ取得することができました。

データを作成し、AWS に登録する

次は、データを作成してみます。

読書リスト画面から、『新規作成』ボタンをクリックすると、モーダルが開き、データを作成できるようにします。

components フォルダに、ModalBook.tsx と、AddBook.tsx を作成します。

UI は、MUI( https://mui.com/components/modal/ 、 https://mui.com/components/text-fields/)を参考にしました。

AddBook.tsx

tsx
import React from "react";
import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import FormControlLabel from "@mui/material/FormControlLabel";
import {
Button,
FormControl,
FormLabel,
MenuItem,
Paper,
Radio,
RadioGroup,
styled,
} from "@mui/material";
export default function AddBook() {
const [title, setTitle] = useState("");
const [author, setAuthor] = useState("");
const [categoryId, setCategoryId] = useState(0);
const [isRead, setIsRead] = useState(false);
const handleChangeTitle = (event: React.ChangeEvent<HTMLInputElement>) => {
setTitle(event.target.value as string);
};
const handleChangeAuthor = (event: React.ChangeEvent<HTMLInputElement>) => {
setAuthor(event.target.value as string);
};
const handleChangeCategory = (event: React.ChangeEvent<HTMLInputElement>) => {
setCategoryId(event.target.value as unknown as number);
};
const handleChangeIsRead = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value === "true";
setIsRead(value);
};
const handleClick = () => {};
return (
<Box
component="form"
sx={{
"& > :not(style)": { m: 1, width: "200ch" },
}}
noValidate
autoComplete="off"
>
<CContainer>
<CPaper>
<TextField
id="title"
label="タイトル"
variant="outlined"
value={title}
onChange={handleChangeTitle}
margin="normal"
fullWidth
/>
<TextField
id="author"
label="著者"
variant="outlined"
value={author}
onChange={handleChangeAuthor}
margin="normal"
fullWidth
/>
<TextField
id="select-Category"
select
label="カテゴリ"
variant="outlined"
value={categoryId}
onChange={handleChangeCategory}
margin="normal"
fullWidth
></TextField>
<Box>
<FormControl component="fieldset">
<FormLabel component="legend">読了</FormLabel>
<RadioGroup
aria-label="gender"
name="controlled-radio-buttons-group"
value={isRead}
onChange={handleChangeIsRead}
row
>
<FormControlLabel value="true" control={<Radio />} label="" />
<FormControlLabel
value="false"
control={<Radio />}
label=""
/>
</RadioGroup>
</FormControl>
</Box>
<Button variant="contained" onClick={handleClick}>
新規追加
</Button>
</CPaper>
</CContainer>
</Box>
);
}
const CContainer = styled(Box)({
display: "flex",
flexDirection: "column",
jusitifyConteng: "center",
alignItems: "center",
position: "absolute",
top: "50%",
left: "50%",
transform: "translateX(-50%) translateY(-50%)",
});
const CPaper = styled(Paper)({
padding: "2rem",
});

ModalBook.tsx

tsx
import * as React from "react";
import Button from "@mui/material/Button";
import Modal from "@mui/material/Modal";
import AddBook from "./AddBook";
export default function BasicModal() {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<div>
<Button onClick={handleOpen} variant="contained">
新規作成
</Button>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<AddBook />
</Modal>
</div>
);
}

読書リスト画面で新規作成ボタンを追加します。

tsx
import ModalBook from "../components/ModalBook";
import { Box } from "@mui/material";
tsx
<CBox>
<Title>読書リスト</Title>
<ModalBook />
</CBox>
tsx
const CBox = styled(Box)({
display: "flex",
justifyContent: "space-between",
});

image6

AddBook.tsx で、カテゴリを選択できるようにします。

Book.tsx と同様に、ams-amplifyawsExprtsをインポートして、AWS AppSyncと連携します。

tsx
import Amplify, { API, graphqlOperation } from "aws-amplify";
import awsExports from "../aws-exports";
Amplify.configure(awsExports);

カテゴリを取得できるようにクエリをインポートしましょう。

tsx
import { listCategories } from "../graphql/queries";

useStatecategoriesの状態を管理します。

fetchCategoriesを作成し、useEffectで呼び出すようにします。

tsx
useEffect(() => {
fetchCategories();
}, []);
const fetchCategories = async () => {
try {
const categoryData: any = await API.graphql(
graphqlOperation(listCategories)
);
const categories = categoryData.data.listCategories.items;
setCategories(categories);
} catch (err) {
console.log("error fetching categories");
}
};

category の型を指定しましょう。

tsx
type TypeCategory = {
id: string;
name: string;
};

リストとして、表示できるようにします。

tsx
<TextField
id="select-Category"
select
label="カテゴリ"
variant="outlined"
value={categoryId}
onChange={handleChangeCategory}
margin="normal"
fullWidth
>
{categories.map((category: TypeCategory) => (
<MenuItem key={category.id} value={category.id}>
{category.name}
</MenuItem>
))}
</TextField>

ブラウザで確認すると、

image7

フォームが作成でき、カテゴリの選択ができました。

『新規追加』ボタンをクリックすると、データを登録できるようにします。

src/grqphql フォルダの mutations.ts から、createBookをインポートします。

tsx
import { createBook } from "../graphql/mutations";

『新規追加』をクリックした後のアクションは、handleClickで操作します。

まずは、JavaScript の不要な挙動を防ぐために、event.preventDefaultを指定しましょう。

tsx
const handleClick = async (event: React.MouseEvent) => {
event.preventDefault();
};

mutations.ts を見てみると、データを作成する場所は、input になっています。

image8

input に title、author、categoryId、isRead が入るようにします。

tsx
const handleClick = async (event: React.MouseEvent) => {
event.preventDefault();
const input = {
title,
author,
categoryId,
isRead,
};
};

APIgraphqlOperationを使って、createBookinputを登録できるようにします。

tsx
const handleClick = async (event: React.MouseEvent) => {
event.preventDefault();
const input = {
title,
author,
categoryId,
isRead,
};
await API.graphql(graphqlOperation(createBook, { input }));
};

では、データを登録してみます。

image9

『新規追加』ボタンをクリックして、モーダルを閉じると、

image10

新規追加したデータが登録できていました。

特定のデータを取得する

次は、詳細画面へ遷移し、特定のデータを表示させます。

まずは、詳細画面を作成します。

BookDetail.tsx

tsx
import * as React from "react";
import { useParams } from "react-router-dom";
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Typography from "@mui/material/Typography";
import { Button } from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
export default function BookDetail() {
const { id } = useParams<{ id?: string | undefined }>();
return (
<Card sx={{ minWidth: 275 }} variant="outlined">
<CardContent>
<Typography sx={{ fontSize: 14 }} color="text.secondary" gutterBottom>
ID:{}
</Typography>
<Typography variant="h5" component="div">
タイトル:{}
</Typography>
<Typography sx={{ mb: 1.5 }} color="text.secondary">
著者:{}
</Typography>
<Typography variant="body2" sx={{ mb: 1.5 }}>
カテゴリ:{}
</Typography>
{/* { ? (
<Typography variant="body2" sx={{ mb: 1.5 }}>
読了:済
</Typography>
) : (
<Typography variant="body2" sx={{ mb: 1.5 }}>
読了:未
</Typography>
)} */}
<Typography variant="body2" sx={{ mb: 1.5 }}>
作成日:
{}
</Typography>
<Button
variant="contained"
color="error"
startIcon={<DeleteIcon />}
onClick={() => {}}
>
削除
</Button>
</CardContent>
</Card>
);
}

読書リスト画面で、『詳細画面』ボタンを作成します。

Book.tsx

tsx
<TableCell>
<Link href={`/book/${book.id}`} underline="hover">
詳細画面
</Link>
</TableCell>

App.tsx で Route を設定しましょう。

tsx
<Route exact path="/book">
<Dashboard>
<Book />
</Dashboard>
</Route>
<Route path="/book/:id">
<Dashboard>
<BookDetail />
</Dashboard>
</Route>

一度、ブラウザで画面遷移を確認します。

image11

『詳細画面』をクリックすると、

BookDetail 画面へ遷移することができました。

ここから、GraphQL で、特定のデータを取得します。

book の型を設定します。

tsx
type Book = {
id: string;
title: string;
author: string;
category: {
name: string;
};
isRead: boolean;
createdAt: string;
};

book を useState で管理します。

tsx
const [book, setBook] = useState<Book | null>(null);

Book.tsx と同様に、ams-amplifyawsExprtsをインポートして、AWS AppSyncと連携します。

tsx
import Amplify, { API, graphqlOperation } from "aws-amplify";
import awsExports from "../aws-exports";
Amplify.configure(awsExports);

getBook をインポートします。

tsx
import { getBook } from "../graphql/queries";

fetchBook を作成します。

src/graphql の queries.ts にあるgetBookを確認すると、idを指定することで特定のデータを取得できるようです。

image12

graphqlOperationには、getBookidを指定します。

tsx
const fetchBook = async () => {
try {
const bookData: any = await API.graphql(graphqlOperation(getBook, { id }));
const book = bookData.data.getBook;
setBook(book);
} catch (err) {
console.log("error fetching books");
}
};

useEffectを設定します。

tsx
useEffect(() => {
fetchBook();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

CardContentの中身を設定しましょう。

tsx
<Card sx={{ minWidth: 275 }} variant="outlined">
<CardContent>
<Typography sx={{ fontSize: 14 }} color="text.secondary" gutterBottom>
ID:{book?.id}
</Typography>
<Typography variant="h5" component="div">
タイトル:{book?.title}
</Typography>
<Typography sx={{ mb: 1.5 }} color="text.secondary">
著者:{book?.author}
</Typography>
<Typography variant="body2" sx={{ mb: 1.5 }}>
カテゴリ:{book?.category.name}
</Typography>
{book?.isRead ? (
<Typography variant="body2" sx={{ mb: 1.5 }}>
読了:済
</Typography>
) : (
<Typography variant="body2" sx={{ mb: 1.5 }}>
読了:未
</Typography>
)}
<Typography variant="body2" sx={{ mb: 1.5 }}>
作成日:
{book?.createdAt}
</Typography>
<Button
variant="contained"
color="error"
startIcon={<DeleteIcon />}
onClick={() => {}}
>
削除
</Button>
</CardContent>
</Card>

ブラウザで確認すると、

image13

id のデータを表示することができました。

データを削除する

次は、データを削除します。

削除ボタンのonClickhandleDeleteを指定しましょう。

tsx
<Button
variant="contained"
color="error"
startIcon={<DeleteIcon />}
onClick={handleDelete}
>
削除
</Button>

handleDelete を作成します。

src/graphql の mutations.ts にあるdeleteBookを確認します。

inputidを指定するとよさそうです。

image14

handleDeleteinputに id指定します。

tsx
const handleDelete = async (event: React.MouseEvent) => {
event.preventDefault();
const input = { id };
};

graphqlOperationdeleteBookinputを指定します。

tsx
const handleDelete = async (event: React.MouseEvent) => {
event.preventDefault();
const input = { id };
await API.graphql(graphqlOperation(deleteBook, { input }));
};

ブラウザで確認しましょう。

image15

読書リスト画面に戻ってみると、

image16

データが削除されていました。

データを更新する

最後に、データを更新します。

読書リストの読了を『済』や『未』へ変更できるようにします。

image17

まずは、『済』と『未』にボタンを設定します。

引数は、book.id と、『済』をクリックした時は false、『未』をクリックした時は true とします。

tsx
{
book.isRead ? (
<TableCell>
<Button size="small" onClick={() => handleUpdate(book.id, false)}>
</Button>
</TableCell>
) : (
<TableCell>
<Button size="small" onClick={() => handleUpdate(book.id, true)}>
</Button>
</TableCell>
);
}

handleUpdateを作成します。

src/graphql フォルダの mutations.ts を確認すると、input でデータを変更することができそうです。

image18

handleUpdate に、先程指定した引数を設定します。

input には、どの id を更新するかを指定するために id と、 更新したい内容の isRead を指定します。

graphqlOperationは、updateBookinputを指定しましょう。

tsx
const handleUpdate = async (id: string, isRead: boolean) => {
const input = { id, isRead };
await API.graphql(graphqlOperation(updateBook, { input }));
};

では、ブラウザで確認します。

image19

読了の『済』をクリックして、リロードすると、

image20

『済』から『未』へ変更することができました。

image21

console.logisReadも、true から false へ更新されました。

© 2024あずきぱんウェブスタジオ