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

React 入門トップページ

« 戻る 次へ »

React 入門

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

コメント編集機能の実装

次に,投稿済みのコメントを編集する機能を作成します.編集は /edit/123 のようにコメント id を指定した URL で行うことにします.このためにまず,src/componenst/CommentEditPage.tsx を作成します.Visual Studio Code ではファイルの先頭に rafce と入力して次の雛形を作成します.

src/componenst/CommentEditPage.tsximport React from 'react'

const CommentEditPage = () => {
  return (
    <div>CommentEditPage</div>
  )
}

export default CommentEditPage

次に編集ページのためのルート情報を追加します.

src/routers/routes.tsximport { Route, Routes } from 'react-router-dom';
import CommentListPage from '../components/CommentListPage';
import CommentShowPage from '../components/CommentShowPage';
import CommentEditPage from '../components/CommentEditPage';

const routes = (
  <Routes>
    <Route path="/" element={<CommentListPage />} />
    <Route path="/show/:commentId" element={<CommentShowPage />} />
    <Route path="/edit/:commentId" element={<CommentEditPage />} />
  </Routes>
);

export default routes;

ブラウザで http://localhost:3000/edit/9/ にアクセスできることを確認します.

ts-2024-56

詳細ページから編集ページへリンクを作成するために,まず Link をインポートします.

src/components/CommentShowPage.tsx(抜粋)import { useParams, useNavigate, Link } from 'react-router-dom';

適当な場所に編集ページへのリンクを設置します.

src/components/CommentShowPage.tsx(抜粋)return (
  <div className="container">
    <h1>コメント</h1>
    <article>
      <div>ID: {data.id}</div>
      <div className="title">Title: {data.title}</div>
      <div className="body">Body: {data.body}</div>
      <div className="body">最終更新: {data.updated_at}</div>
      <div>
        <Link to={`/edit/${data.id}/`}>
          コメント情報の編集
        </Link>
      </div>
    </article>
    <button
      onClick={handleBackClick}
    >コメント一覧へ戻る</button>
  </div>
)

CommentEditPage コンポーネントでは URL に含まれるコメント id が取得できていることを確認します.

src/componenst/CommentEditPage.tsximport React from 'react'
import { useParams } from 'react-router-dom';

const CommentEditPage = () => {

  const {commentId} = useParams<{commentId: string}>();

  return (
    <div>CommentEditPage: {commentId}</div>
  )
}

export default CommentEditPage
ts-2024-57

CommentShowPage コンポーネントと共通する部分が多いですが,まず API から取得した内容を画面に表示するようにします.

src/componenst/CommentEditPage.tsximport React from 'react'
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';

interface CommentData {
  id: number;
  title: string;
  body: string;
}

const CommentEditPage: React.FC = () => {

  const {commentId} = useParams<{commentId: string}>();

  const [data, setData] = useState<CommentData | null>(null);
  const url = `http://127.0.0.1:8000/comments/${commentId}/`;

  useEffect(() => {
    axios.get<CommentData>(url)
      .then(res => setData(res.data))
      .catch(err => console.log(err.message));
  }, [url]);

  if (data === null) {
    return (
      <div className="container">
        <h1>コメントの編集</h1>
        <article>
          <p></p>
        </article>
      </div>
    );
  }

  return (
    <div className="container">
      <h1>コメントの編集</h1>
      <article>
        <div>ID: {data.id}</div>
        <div className="title">Title: {data.title}</div>
        <div className="body">Body: {data.body}</div>
      </article>
    </div>
  )
}

export default CommentEditPage
ts-2024-58

次に,編集のためのフォームを作成します.src/components/EditForm.tsx ファイルを作成してから rafce で雛形を作成します.

src/components/EditForm.tsximport React from 'react'

const EditForm = () => {
  return (
    <div>EditForm</div>
  )
}

export default EditForm

CommentEditPage コンポーネントの先頭で EditForm をインポートします.

src/componenst/CommentEditPage.tsx(抜粋)import EditForm from './EditForm';

return の中で EditForm コンポーネントを配置します.(同時に仮に表示していたタイトルなどは削除します.)

src/componenst/CommentEditPage.tsx(抜粋)return (
  <div className="container">
    <h1>コメントの編集</h1>
    <EditForm />
  </div>
)

EditForm コンポーネントに必要な値を渡すように修正します.

src/componenst/CommentEditPage.tsx(抜粋)return (
  <div className="container">
    <h1>コメントの編集</h1>
    <EditForm
      commentId={data.id}
      title={data.title}
      body={data.body}
      url={url}
    />
  </div>
)

CommentEditPage コンポーネントから EditForm コンポーネントが値を受け取るようにします.このために EditFormProps という名称でインターフェースを作成し,props に型付けを行います.

src/components/EditForm.tsximport React from 'react'

interface EditFormProps {
  commentId: number;
  title: string;
  body: string;
  url: string;
}

const EditForm: React.FC<EditFormProps> = (props) => {
  return (
    <>
      <p>commentId : {props.commentId}</p>
      <p>title : {props.title}</p>
      <p>body : {props.body}</p>
      <p>url : {props.url}</p>
    </>
  )
}

export default EditForm

EditForm コンポーネントにはフォームを配置し,CommentEditPage コンポーネントから渡された値をセットします.ただし,現時点ではフォームを編集できないことに注意してください.

src/components/EditForm.tsximport React from 'react'
import { useState } from 'react';

interface EditFormProps {
  commentId: number;
  title: string;
  body: string;
  url: string;
}

const EditForm: React.FC<EditFormProps> = (props) => {
  const [title, setTitle] = useState<string>(props.title);
  const [body, setBody] = useState<string>(props.body);

  return (
    <>
      <form>
        <div>
          <label>
            タイトル:
            <input
              type="text"
              className="textInput"
              value={title}
            />
          </label>
        </div>
        <div>
          <label>
            本文:
            <input
              type="text"
              className="textInput"
              value={body}
            />
          </label>
        </div>
        <div>
          <button>コメントの更新</button>
        </div>
      </form>
    </>
  )
}

export default EditForm

フォームのテキストボックスを編集できるようにします.

src/components/EditForm.tsximport React from 'react'
import { useState, ChangeEvent } from 'react';

interface EditFormProps {
  commentId: number;
  title: string;
  body: string;
  url: string;
}

const EditForm: React.FC<EditFormProps> = (props) => {
  const [title, setTitle] = useState<string>(props.title);
  const [body, setBody] = useState<string>(props.body);

  const handleTitleChange = (event: ChangeEvent<HTMLInputElement>) => {
    setTitle(event.currentTarget.value);
  };

  const handleBodyChange = (event: ChangeEvent<HTMLInputElement>) => {
    setBody(event.currentTarget.value);
  };

  return (
    <>
      <form>
        <div>
          <label>
            タイトル:
            <input
              type="text"
              className="textInput"
              value={title}
              onChange={handleTitleChange}
            />
          </label>
        </div>
        <div>
          <label>
            本文:
            <input
              type="text"
              className="textInput"
              value={body}
              onChange={handleBodyChange}
            />
          </label>
        </div>
        <div>
          <button>コメントの更新</button>
        </div>
      </form>
    </>
  )
}

export default EditForm

続いて,CommentEditPage にはコメントを更新するための関数を作ります.現時点では実際には更新作業を行わず,EditForm から渡されてきた情報をコンソールに表示する機能だけを実装しておきます.

src/components/CommentEditPage.tsx(抜粋)const handleEditFormSubmit = (url: string, title: string, body: string) => {
  console.log("CommentEditPage : handelEditFormSubmit");
  console.log("url", url);
  console.log("title", title);
  console.log("body", body);
}

その関数を EditForm に渡します.

src/components/CommentEditPage.tsx(抜粋)return (
    <div className="container">
      <h1>コメントの編集</h1>
      <EditForm
        commentId={data.id}
        title={data.title}
        body={data.body}
        url={url}
        onSubmit={handleEditFormSubmit}
      />
    </div>
  )

EditForm のインターフェースではその関数も受け取ることができるようにします.

src/components/EditForm.tsx(抜粋)interface EditFormProps {
  commentId: number;
  title: string;
  body: string;
  url: string;
  onSubmit: (url: string, title: string, body: string) => void;
}

EditForm コンポーネントではボタンが押されたときに formSubmit 関数が実行されるように指定します.

src/components/EditForm.tsx(抜粋)<form onSubmit={formSubmit}>

プログラムの先頭で FormEvent をインポートします.

src/components/EditForm.tsx(抜粋)import { useState, ChangeEvent, FormEvent } from 'react';

EditForm でボタンが押されたときに CommentEditPage にサブミットするための関数 formSubmit を定義します.

src/components/EditForm.tsx(抜粋)const formSubmit = (event: FormEvent<HTMLFormElement>) => {
  event.preventDefault();
  props.onSubmit(props.url, title, body);
};

ブラウザで現時点の動作を確認します.まず,編集画面を開くとコメントの内容がフォームに表示され,テキストボックスを書き換えることができるようになっています.

ts-2024-59

タイトルと本文を変更して「コメントの更新」ボタンを押すと,その内容がコンソールに出力されました.このとき,CommentEditPage でその内容を取得できていることにも注意してください.

ts-2024-60

ここでターミナル(またはコマンドプロンプト)から curl コマンドでコメントの ID を指定してコメント情報を更新する方法を確認しておきます.なお,コマンド最後の /13/ にはコメントの ID を指定することに注意してください.

% curl -X PUT -d "title=API" -d "body=test" http://127.0.0.1:8000/comments/13/ ⏎

実際に API へ更新処理を送信する処理を追加しますが,更新処理後にトップページに移動したいのでまずはプログラムの先頭で useNavigate をインポートします.

src/componenst/CommentEditPage.tsx(抜粋)import { useParams, useNavigate } from 'react-router-dom';

更新処理をコーディングします.

src/componenst/CommentEditPage.tsx(抜粋)const navigate = useNavigate();
const handleEditFormSubmit = (url: string, title: string, body: string) => {
  axios
    .put(url, {
      title: title,
      body: body,
    })
    .then(res => {
      if (res.status === 200) {
        console.log("PUT成功");
        navigate('/');
      } else {
        console.log("PUT失敗");
      }
    })
    .catch(err => {
      console.log(err.message);
      alert("投稿エラー!未入力または文字数超過です.");
    });
}

コメントの更新ができるようになったはずなのでブラウザで確認します.コメントの編集ページでタイトルと本文を書き換えて「コメントの更新」ボタンをクリックします.

ts-2024-61

コメントが更新されて一覧ページに戻りました.

ts-2024-62

EditForm.tsx の全体を示します.

src/components/EditForm.tsximport React from 'react'
import { useState, ChangeEvent, FormEvent } from 'react';

interface EditFormProps {
  commentId: number;
  title: string;
  body: string;
  url: string;
  onSubmit: (url: string, title: string, body: string) => void;
}

const EditForm: React.FC<EditFormProps> = (props) => {
  const [title, setTitle] = useState<string>(props.title);
  const [body, setBody] = useState<string>(props.body);

  const handleTitleChange = (event: ChangeEvent<HTMLInputElement>) => {
    setTitle(event.currentTarget.value);
  };

  const handleBodyChange = (event: ChangeEvent<HTMLInputElement>) => {
    setBody(event.currentTarget.value);
  };

  const formSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    props.onSubmit(props.url, title, body);
  };

  return (
    <>
      <form onSubmit={formSubmit}>
        <div>
          <label>
            タイトル:
            <input
              type="text"
              className="textInput"
              value={title}
              onChange={handleTitleChange}
            />
          </label>
        </div>
        <div>
          <label>
            本文:
            <input
              type="text"
              className="textInput"
              value={body}
              onChange={handleBodyChange}
            />
          </label>
        </div>
        <div>
          <button>コメントの更新</button>
        </div>
      </form>
    </>
  )
}

export default EditForm

CommentEditPage.tsx の全体を示します.

src/componenst/CommentEditPage.tsximport React from 'react'
import { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import axios from 'axios';
import EditForm from './EditForm';

interface CommentData {
  id: number;
  title: string;
  body: string;
}

const CommentEditPage: React.FC = () => {

  const {commentId} = useParams<{commentId: string}>();

  const [data, setData] = useState<CommentData | null>(null);
  const url = `http://127.0.0.1:8000/comments/${commentId}/`;

  useEffect(() => {
    axios.get<CommentData>(url)
      .then(res => setData(res.data))
      .catch(err => console.log(err.message));
  }, [url]);

  const navigate = useNavigate();
  const handleEditFormSubmit = (url: string, title: string, body: string) => {
    axios
      .put(url, {
        title: title,
        body: body,
      })
      .then(res => {
        if (res.status === 200) {
          console.log("PUT成功");
          navigate('/');
        } else {
          console.log("PUT失敗");
        }
      })
      .catch(err => {
        console.log(err.message);
        alert("投稿エラー!未入力または文字数超過です.");
      });
  }

  if (data === null) {
    return (
      <div className="container">
        <h1>コメントの編集</h1>
        <article>
          <p></p>
        </article>
      </div>
    );
  }

  return (
    <div className="container">
      <h1>コメントの編集</h1>
      <EditForm
        commentId={data.id}
        title={data.title}
        body={data.body}
        url={url}
        onSubmit={handleEditFormSubmit}
      />
    </div>
  )
}

export default CommentEditPage

なお,Python Django で開発した API 側でスリープ処理を入れている場合は,画面が切り替わるまでに時間がかかります.その間,ユーザはシステムの状態を理解することができないので,処理中であることがわかるように何らかの表示がされるようにしてもよいでしょう.

目次に戻る