タグ: REST API

  • WordPressの全記事一覧をREST APIを使って取得する

    WordPress REST APIを使用して投稿データを取得し、ページネーション付きで一覧表示するためのものです。WordPressのサイトURLとREST APIのエンドポイントを設定し、axiosライブラリを使ってデータを非同期で取得します。ユーザーは、ページネーションボタンをクリックすることで、異なるページの投稿を閲覧できます。取得したHTMLコンテンツ(タイトルや抜粋)は、セキュリティのためDOMPurifyを使ってサニタイズ(無害化)されてから表示されます。

    環境

    vite@7.1.11
    react@19.2.0
    axios@1.12.2
    dompurify@3.3.0
    jsdom@27.0.1

    リポジトリはこちら

    主要な機能とコンポーネントの構造

    App.jsx

    import React, { useState, useEffect, useCallback } from 'react';
    import axios from 'axios';
    import './App.css'
    
    function App() {
      // WordPressサイトのベースURLを設定
      const API_BASE_URL = 'https://xxxxxx/wp-json/wp/v2';
      const ITEMS_PER_PAGE = 10; // 1ページあたりの表示件数
    
      const [posts, setPosts] = useState([]);
      const [currentPage, setCurrentPage] = useState(1);
      const [totalPages, setTotalPages] = useState(1); // 全ページ数
      const [isLoading, setIsLoading] = useState(false);
      const [error, setError] = useState(null);
    
      /**
       * 投稿データを取得する関数
       */
      const fetchPosts = useCallback(async (page) => {
    
        setIsLoading(true);
        setError(null);
    
        try {
    
          const response = await axios.get(`${API_BASE_URL}/posts`, {
            params: {
              page: page,
              per_page: ITEMS_PER_PAGE,
              _embed: true, // アイキャッチ画像などの埋め込み情報を取得
            },
          });
    
          setPosts(response.data);// 取得した投稿データをセット
    
          // レスポンスヘッダーから全ページ数と全投稿数を取得
          const totalPagesFromHeader = parseInt(response.headers['x-wp-totalpages'], 10);// 'x-wp-totalpages' ヘッダーには、このクエリに該当する総ページ数が含まれる
          setTotalPages(totalPagesFromHeader);
    
        } catch (err) {
    
          console.error("API Fetch Error:", err);
          setError("データの取得中にエラーが発生しました。");
    
        } finally {
    
          setIsLoading(false);
        }
      }, []);
    
      useEffect(() => {// ページ番号が変わるたびにデータを取得
    
        fetchPosts(currentPage);
      }, [currentPage, fetchPosts]);
    
      const handlePageChange = (pageNumber) => {
    
        if (pageNumber >= 1 && pageNumber <= totalPages) {// ページ番号の範囲チェック
          
          setCurrentPage(pageNumber);
          window.scrollTo(0, 0);// スクロールをトップに戻すなど
        }
      };
    
      if (isLoading) return <p>記事を読み込み中...</p>;
      if (error) return <p style={{ color: 'red' }}>エラー: {error}</p>;
      if (posts.length === 0) return <p>記事が見つかりませんでした。</p>;
    
      const pageNumbers = Array.from({length: totalPages}, (_, i) => i + 1);// ページ番号ボタンをレンダリングするための配列を生成
    
      return (
        <div>
          <h1>投稿一覧 (WP REST API)</h1>
    
          {/* 投稿リストの表示 */}
          {posts.map(post => (
            <div key={post.id} style={{ border: '1px solid #ccc', margin: '10px 0', padding: '15px' }}>
              {/* タイトルはHTMLエスケープされている可能性があるため、dangerouslySetInnerHTMLを使用 */}
              <h2 dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
              {/* 抜粋も同様 */}
              <p dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }} />
              {/* アイキャッチ画像 (_embedが効いている場合) */}
              {post._embedded && post._embedded['wp:featuredmedia'] && (
                <img 
                  src={post._embedded['wp:featuredmedia'][0].source_url} 
                  alt={post._embedded['wp:featuredmedia'][0].alt_text}
                  style={{ maxWidth: '200px' }}
                />
              )}
              <a href={post.link} target="_blank" rel="noopener noreferrer">詳細へ</a>
            </div>
          ))}
    
          {/* ページネーションコントロール */}
          <nav style={{ margin: '20px 0' }}>
            <button
              onClick={() => handlePageChange(currentPage - 1)}
              disabled={currentPage === 1}
            >
              « 前へ
            </button>
    
            {/* ページ番号ボタンのリスト */}
            {pageNumbers.map(number => (
              <button
                key={number}
                onClick={() => handlePageChange(number)}
                style={{
                  fontWeight: currentPage === number ? 'bold' : 'normal',
                  backgroundColor: currentPage === number ? '#0073aa' : '#f0f0f0',
                  color: currentPage === number ? 'white' : 'black',
                  border: '1px solid #ccc',
                  margin: '0 4px',
                  padding: '8px 12px',
                  cursor: 'pointer'
                }}
              >
                {number}
              </button>
            ))}
    
            <button
              onClick={() => handlePageChange(currentPage + 1)}
              disabled={currentPage === totalPages}
            >
              次へ »
            </button>
          </nav>
          <p>現在 {totalPages} ページ中 {currentPage} ページ目を表示中</p>
        </div>
      );
    }
    
    export default App;

    Appコンポーネントは、WordPressのサイトURLとREST APIのエンドポイントを設定し、axiosライブラリを使ってデータを非同期で取得します 。ユーザーは、ページネーションボタンをクリックすることで、異なるページの投稿を閲覧できます。

    状態管理 (useState)

    アプリケーションの状態を管理するための主要なステート変数です。

    posts
    取得した投稿データ(配列)を格納します。

    currentPage
    現在表示中のページ番号を管理します。

    totalPages
    APIレスポンスのヘッダーから取得した総ページ数を格納します。

    isLoading
    データ取得中(ローディング状態)かを示すブール値です。

    error
    データ取得時にエラーが発生した場合のエラーメッセージを格納します。

    定数

    API_BASE_URL
    WordPress REST APIのベースURL 。

    ITEMS_PER_PAGE
    1ページあたりの表示件数 。

    データの取得 (fetchPosts / useEffect)

    fetchPosts 関数
    useCallback を使用し、指定されたページ番号の投稿データを非同期で取得します 。axios.get/posts エンドポイントに対し、page(現在のページ番号)、per_page(表示件数)、_embed: true(埋め込み情報)をクエリパラメータとしてリクエストを送信し、成功時にはレスポンスヘッダー x-wp-totalpages から全ページ数を取得して setTotalPages で更新します。

      useEffect フック
      currentPage の値が変更されるたびに fetchPosts が実行され、新しいページのデータが取得されます 。

      ページネーションの操作 (handlePageChange)

      この関数は、指定された pageNumber が 1 以上かつ totalPages 以下の有効な範囲内であるかチェックした上で currentPage ステートを更新し、同時に window.scrollTo(0, 0) で画面をトップへスクロールします。

        UIのレンダリング

        状態に応じた表示

        データの読み込み中、エラー発生時、または記事が 0 件の場合に、それぞれ対応するメッセージを表示します 。

        投稿リストの表示

        posts.map を使用して、取得した投稿のリストを表示します。セキュリティ対策として、各投稿のタイトルと抜粋はインポートしたsanitizeHtml関数でサニタイズ(無害化)され、そのサニタイズされたHTMLはdangerouslySetInnerHTMLでレンダリングされます。アイキャッチ画像は、post._embedded['wp:featuredmedia']の情報がある場合にそのsource_urlを使って表示し、post.linkに基づいた詳細ページへのリンクも提供します。

          ページネーションコントロール

          このページネーションコンポーネントは、総ページ数(totalPages)に基づいてページ番号ボタンを動的に生成し、現在アクティブなページには異なるスタイルを適用します。「前へ」/「次へ」ボタンは handlePageChange 関数でページ番号を増減させますが、現在のページが最初(1)または最後(totalPages)の場合は disabled 属性で無効化されます。また、コンポーネント下部には現在表示中のページと総ページ数が表示されます。

          sanitizeHtml 関数

          この処理では、まずHTMLサニタイズ(無害化)のエンジンとして DOMPurify ライブラリをインポートします。危険性のあるHTML文字列(dirtyHtml)を受け取ると、DOMPurify.sanitize(dirtyHtml) を呼び出します。これにより、<script>タグや onerror 属性といった悪意のあるコードが自動的に除去されます。こうして無害化された安全なHTML文字列(cleanHtml)が App.jsx に返され、投稿リストで dangerouslySetInnerHTML を使って安全に表示されます。