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 を使って安全に表示されます。