View on GitHub

ページネーションの作成

Home

ページネーションの作成

定数の定義

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 にアクセスしてください。

サインインすると、映画リストが表示されます。また、左右のボタンを押すことでページが切り替わります。