神戸学院大学 経営学部 林坂ゼミ

React 入門トップページ

« 戻る 次へ »

React 入門

TypeScript コメント掲示板アプリの開発

コメント削除機能の実装

コメントの投稿機能が実現できたので,次は削除機能を実装してきます.

まず,コメント一つひとつに対して削除するためのボタンを配置したいので,Comment コンポーネントを次の通り編集します.具体的には,26〜32行目のように deleteButton という名前のクラスを設定した <div> 要素を配置し,その中にボタンを設置します.そのボタンをクリックしたときに handleClickDeleteButton 関数が実行されるように指定し,その関数を14〜16行目のとおり定義します.この関数ではとりあえずどのボタンがクリックされたかをコンソールにログとして出力する処理だけを入力しておきます.

src/components/Comment.tsximport 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 がコンソールに出力されることも確認してください.

ts-2024-25

上では 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 コンポーネントには propsonDelete 関数が渡されるようにしたいので,インターフェースにそのことを追加します.

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);
  };
ts-2024-26

ここまでの作業の結果,どのコメントに対する削除ボタンがクリックされたかを 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 を更新した結果,画面の表示が更新されることになります.

ブラウザで動作を確認します.コメントを削除する前に新規のコメントを追加しておきます.

ts-2024-27

ts-2024-28

実際にコメントが削除されたことを確認します.

ts-2024-29

ここでも少々複雑な処理になりました.現時点での CommentListPage.tsx 全体を示しておきます.

src/components/CommentListPage.tsximport 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.tsximport 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

目次に戻る