ページネーションの作成
定数の定義
src/frontend/src/constants/styles.ts
/**
* スタイル関連の定数
*/
export const STYLES = {
...(略)...
// 映画リスト関連
LIST_MOVIE: 'mx-4 my-4 flex justify-between',
LIST_MOVIE_LABEL_PHRASE: 'text-lg font-bold',
LIST_MOVIE_INSIDE: 'flex flex-1 flex-wrap justify-center gap-2',
// ↓追加
LIST_MOVIE_BUTTON_PREV: `cursor-pointer rounded-l-lg bg-indigo-400 px-2 py-1 font-semibold text-white hover:bg-indigo-600 focus:ring-4 focus:ring-indigo-400 disabled:cursor-not-allowed disabled:bg-gray-300`,
LIST_MOVIE_BUTTON_NEXT: `cursor-pointer rounded-r-lg bg-indigo-400 px-2 py-1 font-semibold text-white hover:bg-indigo-600 focus:ring-4 focus:ring-indigo-400 disabled:cursor-not-allowed disabled:bg-gray-300`,
// ↑追加
LIST_MOVIE_PER_PAGE_DEFAULT: 5,
...(略)...
} as const;
コンポーネントの作成
映画リストコンポーネント
src/frontend/src/app/components/list/ListMovie.tsx
"use client";
import { useEffect, useState, useCallback } from "react"; // <- useCallbackを追加
import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos"; // <- 追加
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; // <- 追加
import { Movie } from "@/types/data";
import { STYLES } from "@/constants";
import CardMovie from "../card/CardMovie";
interface Props {
phrase: string;
movies: Movie[];
}
/**
* 映画リストコンポーネント
*/
const ListMovie = (props: Props) => {
const [movies, setMovies] = useState<Movie[]>(props.movies);
const [perPage, setPerPage] = useState<number>(
STYLES.LIST_MOVIE_PER_PAGE_DEFAULT,
);
const [currentMovies, setCurrentMovies] = useState<Movie[]>([]);
const [currentPage, setCurrentPage] = useState(0); // <- 追加
useEffect(() => {
setMovies(props.movies);
}, [props.movies]);
useEffect(() => {
if (movies.length === 0 || perPage <= 0) {
setCurrentMovies([]);
return;
}
const start = perPage * currentPage; // <- 修正
const end = start + perPage;
const currentMovies_ = movies.slice(start, end);
setCurrentMovies(currentMovies_);
}, [movies, perPage, currentPage]); // <- currentPageを追加
// ↓追加
// 総ページ数の計算
const totalPages =
movies.length === 0 || perPage <= 0
? 0
: Math.ceil(movies.length / perPage);
/**
* ページ変更ハンドラ
* @param page - 移動先のページ番号
*/
const handlePageChange = useCallback(
(page: number) => {
if (totalPages === 0) return;
let newPage = page;
// 負の値の場合は最後のページに
if (newPage < 0) {
newPage = Math.max(totalPages - 1, 0);
}
// 最大ページ数を超える場合は最初のページに
if (newPage >= totalPages) {
newPage = 0;
}
setCurrentPage(newPage);
},
[totalPages],
);
// ↑追加
// 空の状態の表示
if (movies.length === 0) {
return <></>;
}
return (
<>
<div className={`${STYLES.LIST_MOVIE_LABEL_PHRASE}`}>{props.phrase}</div>
<div className={`${STYLES.LIST_MOVIE}`}>
{/* ↓追加 */}
<button
className={`${STYLES.LIST_MOVIE_BUTTON_PREV}`}
onClick={() => handlePageChange(currentPage - 1)}
disabled={totalPages <= 1}
type="button"
>
<ArrowBackIosIcon />
</button>
{/* ↑追加 */}
<div className={`${STYLES.LIST_MOVIE_INSIDE}`}>
{currentMovies.map((movie, index) => (
<CardMovie movie={movie} key={movie.movie_id} />
))}
</div>
{/* ↓追加 */}
<button
className={`${STYLES.LIST_MOVIE_BUTTON_NEXT}`}
onClick={() => handlePageChange(currentPage + 1)}
disabled={totalPages <= 1}
type="button"
>
<ArrowForwardIosIcon />
</button>
{/* ↑追加 */}
</div>
</>
);
};
export default ListMovie;
ブラウザで下記 URL にアクセスしてください。
サインインすると、映画リストが表示されます。また、左右のボタンを押すことでページが切り替わります。