React 入門
TypeScript コメント掲示板アプリの開発
コメント削除機能の実装
コメントの投稿機能が実現できたので,次は削除機能を実装してきます.
まず,コメント一つひとつに対して削除するためのボタンを配置したいので,Comment コンポーネントを次の通り編集します.具体的には,26〜32行目のように deleteButton
という名前のクラスを設定した <div>
要素を配置し,その中にボタンを設置します.そのボタンをクリックしたときに handleClickDeleteButton
関数が実行されるように指定し,その関数を14〜16行目のとおり定義します.この関数ではとりあえずどのボタンがクリックされたかをコンソールにログとして出力する処理だけを入力しておきます.
src/components/Comment.tsx
import React from 'react'
interface CommentProps {
comment: {
id: number;
title: string;
body: string;
updated_at: string;
};
}
const Comment: React.FC<CommentProps> = (props) => {
const handleClickDeleteButton = () => {
console.log("handleClickDeleteButton", props.comment.id);
};
return (
<article>
<div className="title">
{props.comment.title}
</div>
<div className="body">
{props.comment.body}
</div>
<div className="deleteButton">
<button
onClick={handleClickDeleteButton}
>
削除
</button>
</div>
</article>
)
}
export default Comment
削除ボタンは画面の右側に配置したいので,index.css にスタイルを追加します.
src/index.css(抜粋)
.deleteButton {
text-align: right;
}
画面の右側に削除ボタンが配置されました.ブラウザの横幅を変化させて配置を確認するとともに,削除ボタンを押したときにそのコメントの id がコンソールに出力されることも確認してください.
上では Comment コンポーネントで削除したいコメントの ID を取得しています.実際の削除処理は CommentListPage コンポーネントで行う必要があるので,CommentListPage コンポーネントに handleDeleteButtonClick 関数を定義し,Comment コンポーネントに onDelete
という名前で関数を渡します.なお,handleDeleteButtonClick 関数の定義は commentItems の定義よりも上に入力しなければならないことに注意してください.
src/components/CommentListPage.tsx(抜粋)
const handleDeleteButtonClick = (commentId: number) => {
console.log('handleDeleteButtonClick', commentId);
};
const commentItems = results.results.map((comment) => {
return (
<Comment
key={comment.id}
comment={comment}
onDelete={handleDeleteButtonClick}
/>
)
});
Comment コンポーネントには props
で onDelete
関数が渡されるようにしたいので,インターフェースにそのことを追加します.
src/components/Comment.tsx(抜粋)
interface CommentProps {
comment: {
id: number;
title: string;
body: string;
updated_at: string;
};
onDelete: (id: number) => void;
}
Comment コンポーネントからは onDelete
でコメントの id を CommentListPage コンポーネントに渡します.
src/components/Comment.tsx(抜粋)
const Comment: React.FC<CommentProps> = (props) => {
const handleClickDeleteButton = () => {
// console.log("handleClickDeleteButton", props.comment.id);
props.onDelete(props.comment.id);
};
ここまでの作業の結果,どのコメントに対する削除ボタンがクリックされたかを CommentListPage コンポーネントで検知できるようになりました.続いて,実際に削除を行うための処理をコーディングします.
src/components/CommentListPage.tsx(抜粋)
const handleDeleteButtonClick = (commentId: number) => {
if (!window.confirm("削除しますか?")) {
return;
}
const newComments = [...results.results].filter((comment) => {
return comment.id !== commentId;
});
const newResults: Results = {
"count": results.count - 1, // コメント数は1減らす
"previous": results.previous,
"next" : results.next,
"results": newComments
};
setResults(newResults);
};
上のコードの詳細について説明します.5行目の [...results.results]
はスプレッド構文です.コメントの一覧を配列に展開しています.filter
ではコメントの配列を一つずつ順番に取り出して comment
に代入し,削除対象の commentID
と等しくないものを配列にして返して newComments
オブジェクト定数を作成しています.その結果として削除対象のコメントだけが削除されたことになります.その後,8行目で newResults
オブジェクトを生成するときに,対象コメントが削除された残りのコメント配列オブジェクト newComments
を指定しています.最後に14行目の setResults()
で results
を更新した結果,画面の表示が更新されることになります.
ブラウザで動作を確認します.コメントを削除する前に新規のコメントを追加しておきます.
実際にコメントが削除されたことを確認します.
ここでも少々複雑な処理になりました.現時点での CommentListPage.tsx 全体を示しておきます.
src/components/CommentListPage.tsx
import React from 'react'
import { useState } from 'react';
import Comment from './Comment';
import CreateForm from './CreateForm';
interface CommentData {
id: number;
title: string;
body: string;
updated_at: string;
}
interface Results {
count: number;
next: string | null;
previous: string | null;
results: CommentData[];
}
const CommentListPage: React.FC = () => {
const [results, setResults] = useState<Results>({
"count":10,
"next":"http://127.0.0.1:8000/comments/?page=2",
"previous":null,
"results":[
{
"id":9,
"title":"9個目のコメント",
"body":"コメントの本文9",
"updated_at":"2023-11-21T11:20:00"
},
{
"id":10,
"title":"10個目のコメント",
"body":"コメントの本文10",
"updated_at":"2023-11-21T11:10:00"
}
]
});
const handleDeleteButtonClick = (commentId: number) => {
if (!window.confirm("削除しますか?")) {
return;
}
const newComments = [...results.results].filter((comment) => {
return comment.id !== commentId;
});
const newResults: Results = {
"count": results.count - 1, // コメント数は1減らす
"previous": results.previous,
"next" : results.next,
"results": newComments
};
setResults(newResults);
};
const commentItems = results.results.map((comment) => {
return (
<Comment
key={comment.id}
comment={comment}
onDelete={handleDeleteButtonClick}
/>
)
});
const handelCreateFormSubmit = (title: string, body: string) => {
const newComments = [...results.results]; // スプレッド構文でコメントの配列だけを取り出す
newComments.unshift({ // unshift で先頭に追加,push では最後に追加
id: Date.now(),
title: title,
body: body,
updated_at: "2024-03-18T12:00:00",
});
const newResults = {
"count": results.count + 1, // コメント数は1増やす
"previous": results.previous,
"next" : results.next,
"results": newComments, // これがコメントの配列
};
setResults(newResults); // 画面を更新
}
return (
<div className="container">
<h1>コメント一覧</h1>
{commentItems}
<CreateForm
onSubmit={handelCreateFormSubmit}
/>
</div>
)
}
export default CommentListPage
Comment.tsx の全体も示しておきます.
src/components/Comment.tsx
import React from 'react'
interface CommentProps {
comment: {
id: number;
title: string;
body: string;
updated_at: string;
};
onDelete: (id: number) => void;
}
const Comment: React.FC<CommentProps> = (props) => {
const handleClickDeleteButton = () => {
props.onDelete(props.comment.id);
};
return (
<article>
<div className="title">
{props.comment.title}
</div>
<div className="body">
{props.comment.body}
</div>
<div className="deleteButton">
<button
onClick={handleClickDeleteButton}
>
削除
</button>
</div>
</article>
)
}
export default Comment