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

React 入門トップページ

« 戻る 次へ »

React 入門

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

ボタンクリックを処理して表示内容を更新する

前のページで設置した「次のページ」ボタンをクリックしたことを検知し表示内容を更新するコードを作成します.同時にボタンのラベルにはリンク先のURLも一時的に表示しておきます.まず,ボタンに onClick 属性を追加し,実行したい関数名 handleCurrentPageNext{ ... } で囲って与えます.

src/components/CommentListPage.js(抜粋)<div className="commentsHeader">
  <div>コメント総数:{results.count}</div>
  <div>
    <button>前のページ ({results.previous})</button>
    <button
      onClick={handleNextPageButton}
    >次のページ ({results.next})</button>
  </div>
</div>

次に,上で指定した関数 handleNextPageButton を定義します.定義する場所は CommentListPage() 関数内で,return 文よりも前です.

src/components/CommentListPage.js(抜粋)const handleNextPageButton = () => {
  console.log("handleNextPageButton :", results.next);
};

これでクリックされたことと,次にリクエストすべき API の URL が取得できました.

react-2024-56

実際に API にリクエストして表示内容を更新します.

src/components/CommentListPage.js(抜粋)const handleNextPageButton = () => {
  // console.log("handleNextPageButton :", results.next);
  axios.get(results.next)
    .then(res => setResults(res.data))
    .catch(err => console.log(err.message));
};

ページを進めると「前のページ」と「次のページ」の URL が取得できていることもわかります.

react-2024-57

しかし,このままでは最後のページまで進んだ後に「次のページ」ボタンをクリックすると,リクエストの URL が null 値であるために処理に失敗します.同時にコンソールには「Cannot read properties of null (reading 'protocol')」と表示されます.

react-2024-58

したがって,リクエスト送信前に URL が null であるかどうかをチェックしておくとよいでしょう.

src/components/CommentListPage.js(抜粋)const handleNextPageButton = () => {
  if (results.next === null) {
    return;
  }
  axios.get(results.next)
    .then(res => setResults(res.data))
    .catch(err => console.log(err.message));
};

ここで一旦コード全体を確認します.

src/components/CommentListPage.jsimport React from 'react'
import { useState, useEffect } from 'react';
import axios from 'axios';
import Comment from './Comment';
import CreateForm from './CreateForm';

const CommentListPage = () => {

  const [results, setResults] = useState({
    "count":10,
    "next":"http://127.0.0.1:8000/comments/?page=2",
    "previous":null,
    "results":[
      {
        "id":9999,
        "title":"ダミー",
        "body":"ダミーの本文",
        "updated_at":"2023-11-21T11:20:00"
      },
      {
        "id":10000,
        "title":"ダミーのタイトル",
        "body":"ダミーです",
        "updated_at":"2023-11-21T11:10:00"
      }
    ]
  });

  useEffect(() => {
    const url = "http://127.0.0.1:8000/comments/";
    axios.get(url)
        .then(res => setResults(res.data))
        .catch(err => console.log(err.message));
  }, []);

  const handleDeleteButtonClick = (commentId) => {
    if (!window.confirm("削除しますか?")) {
      return;
    }
    const newComments = [...results.results].filter((comment) => {
      return comment.id !== commentId;
    });
    const newResults = {
      "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, body) => {
    const newComments = [...results.results];  // スプレッド構文でコメントの配列だけを取り出す
    newComments.unshift({   // unshift で先頭に追加,push では最後に追加
      id: Date.now(),
      title: title,
      body: body,
      updated_at: "2024-02-25T15:30:00",
    });
    const newResults = {
      "count": results.count + 1, // コメント数は1増やす
      "previous": results.previous,
      "next" : results.next,
      "results": newComments,   // これがコメントの配列
    };
    setResults(newResults); // 画面を更新
  }

  const handleNextPageButton = () => {
    if (results.next === null) {
      return;
    }
    axios.get(results.next)
      .then(res => setResults(res.data))
      .catch(err => console.log(err.message));
  };

  return (
    <div className="container">
      <h1>コメント一覧</h1>

      <div className="commentsHeader">
        <div>コメント総数:{results.count}</div>
        <div>
          <button>前のページ ({results.previous})</button>
          <button
            onClick={handleNextPageButton}
          >次のページ ({results.next})</button>
        </div>
      </div>

      {commentItems}

      <CreateForm
        onSubmit={handelCreateFormSubmit}
      />
    </div>
  )
}

export default CommentListPage

「次のページ」ボタンの動作ができたので「前のページ」ボタンの処理もほぼ同じ方法でコーディングします.

src/components/CommentListPage.jsimport React from 'react'
import { useState, useEffect } from 'react';
import axios from 'axios';
import Comment from './Comment';
import CreateForm from './CreateForm';

const CommentListPage = () => {

  const [results, setResults] = useState({
    "count":10,
    "next":"http://127.0.0.1:8000/comments/?page=2",
    "previous":null,
    "results":[
      {
        "id":9999,
        "title":"ダミー",
        "body":"ダミーの本文",
        "updated_at":"2023-11-21T11:20:00"
      },
      {
        "id":10000,
        "title":"ダミーのタイトル",
        "body":"ダミーです",
        "updated_at":"2023-11-21T11:10:00"
      }
    ]
  });

  useEffect(() => {
    const url = "http://127.0.0.1:8000/comments/";
    axios.get(url)
        .then(res => setResults(res.data))
        .catch(err => console.log(err.message));
  }, []);

  const handleDeleteButtonClick = (commentId) => {
    if (!window.confirm("削除しますか?")) {
      return;
    }
    const newComments = [...results.results].filter((comment) => {
      return comment.id !== commentId;
    });
    const newResults = {
      "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, body) => {
    const newComments = [...results.results];  // スプレッド構文でコメントの配列だけを取り出す
    newComments.unshift({   // unshift で先頭に追加,push では最後に追加
      id: Date.now(),
      title: title,
      body: body,
      updated_at: "2024-02-25T15:30:00",
    });
    const newResults = {
      "count": results.count + 1, // コメント数は1増やす
      "previous": results.previous,
      "next" : results.next,
      "results": newComments,   // これがコメントの配列
    };
    setResults(newResults); // 画面を更新
  }

  const handlePrevPageButton = () => {
    if (results.previous === null) {
      return;
    }
    axios.get(results.previous)
      .then(res => setResults(res.data))
      .catch(err => console.log(err.message));
  };

  const handleNextPageButton = () => {
    if (results.next === null) {
      return;
    }
    axios.get(results.next)
      .then(res => setResults(res.data))
      .catch(err => console.log(err.message));
  };

  return (
    <div className="container">
      <h1>コメント一覧</h1>

      <div className="commentsHeader">
        <div>コメント総数:{results.count}</div>
        <div>
          <button
            onClick={handlePrevPageButton}
          >前のページ ({results.previous})</button>
          <button
            onClick={handleNextPageButton}
          >次のページ ({results.next})</button>
        </div>
      </div>

      {commentItems}

      <CreateForm
        onSubmit={handelCreateFormSubmit}
      />
    </div>
  )
}

export default CommentListPage

目次に戻る