1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【React + Contextで無限スクロール付きスレッド一覧を実装してみた】

Posted at

Reactアプリで「スレッド一覧」を表示し、スクロールが一番下に到達したときに次のスレッドを自動取得する「無限スクロール」を実装する。データはバックエンドAPIから offset と limit を使ってページごとに取得する構成。

// //ここでthreadsの情報データベース内の情報を保持
import { createContext, useState, useEffect } from "react";

// コンテキストを作成
export const ThreadsContext = createContext();

export const ThreadsProvider = ({ children }) => {
  const [getthreads, setGetThreads] = useState([]);//スレッドの一覧データ。初期値は空配列。
  const [offset, setOffset] = useState(0);//ページネーションの開始位置。初期値は 0。
  const [isLoading, setIsLoading] = useState(false);//現在データ取得中かどうかを管理するためのフラグ。多重呼び出し防止にも。
  const [hasMore, setHasMore] = useState(true); // ← 終端検知用
  const limit = 3;//一度に取得したいスレッドの件数。

  const fetchThreads = async (offset = 0, limit = 3) => {//非同期でスレッドを取得する関数。引数で offset/limit を受け取る。
    try {
      const response = await fetch(
        `http://localhost:8080/app/threads?offset=${offset}&limit=${limit}`,
        {
          method: "GET",
          headers: {
            Authorization: `Bearer ${localStorage.getItem("jwtToken")}`,
            "Content-Type": "application/json",
          },
        }
      );

      if (!response.ok) {
        throw new Error("スレッド一覧の取得に失敗しました");
      }

      const data = await response.json();
      console.log("取得したスレッド一覧:", data);

      //dataがない場合(投稿がもうない場合)
      if (data.length === 0) {
        setHasMore(false); // これ以上読み込まない
      } else {
        setGetThreads((prev) => {//すでに取得済みの ID を除外してから新しいスレッドを追加。重複を防ぐ。
          const existingIds = new Set(prev.map((t) => t.id));
          const deduped = data.filter((t) => !existingIds.has(t.id));
          return [...prev, ...deduped];
        });
      }
    } catch (error) {
      console.error("エラー:", error);
    }
  };

  //データ取得トリガー
  //offset が変わるたびにスレッドを読み込む。isLoading と hasMore を使って不要な呼び出しを防止。
  useEffect(() => {
    const loadThreads = async () => {
      if (!isLoading && hasMore) {
        setIsLoading(true);
        await fetchThreads(offset, limit);
        setIsLoading(false);
      }
    };
    loadThreads();
  }, [offset]);


  //スクロール検知
  useEffect(() => {
    const handleScroll = () => {
      const scrollThreshold = 100;
      const scrolledToBottom =
        window.innerHeight + document.documentElement.scrollTop >=
        document.documentElement.offsetHeight - scrollThreshold;

      if (scrolledToBottom && !isLoading && hasMore) {
        //画面の一番下に近づいたことを検知する
        console.log("一番下!");
        setOffset((prev) => prev + limit);
      }
    };

    window.addEventListener("scroll", handleScroll);
    //スクロールが条件を満たしたら offset を更新し、次のスレッドをロードする準備。
    return () => window.removeEventListener("scroll", handleScroll);
  }, [isLoading, hasMore]); // ← フラグを依存に

  return (
    <ThreadsContext.Provider value={{ getthreads, fetchThreads }}>
      {children}
    </ThreadsContext.Provider>
  );
};

・offset / limit ページネーションの起点と件数。API に渡して部分取得を実現。
・isLoading API 多重呼び出しを防ぐためのフラグ。
・hasMore API が空データを返したとき、それ以上呼ばせないようにするための終端チェック。
・Set による重複除去 同じスレッド ID を2回表示させないために、取得済みの ID と照合してフィルタリング。

バックエンド側の設定

 public List<ThreadListDto> getThreads(Integer offset, Integer limit) {//外部から offset と limit を受け取り、スレッド一覧のDTOをリストとして返すメソッド。
        Pageable pageable = PageRequest.of(offset / limit, limit, Sort.by(Sort.Direction.DESC, "postedAt"));
        return threadsRepository.getThreads(pageable);
        //getThreadsは、スレッドリポジトリのfindThreadsの中身を返す
    }

こちらがサービスクラスの中身です!
PageRequest.of(page, size) は Spring Data のページングの仕組み。

ここで offset / limit をしているのは、クライアントが送ってくる「何件目から」を、Springが要求する「ページ番号」に変換しているため(ページ数 = offset ÷ limit)。

第3引数で postedAt の降順(新しい順)にソートしてる。
例:offset=6, limit=3 なら PageRequest.of(2, 3) → 3件ずつの3ページ目(7〜9件目)を取得。

簡単にではありますが、備忘録として記載しておきm

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?