prgrms-fe-devcourse / NFE1-1-3-Calmiary

Calmiary - 고민 기록 다이어리 🦋
2 stars 0 forks source link

무한스크롤 관련 정리 #48

Open ruehan opened 7 hours ago

ruehan commented 7 hours ago

커뮤니티를 기준으로 무한스크롤 예시를 만들어봤습니다. 실제 코드와 다른 부분이 있으니 참고용으로 사용하시면 좋을 듯 합니다.

// types
export interface UserInfo {
  nickname: string;
  profile_image: string;
}

export interface Post {
  id: number;
  user_id: number;
  emotion_type: string;
  content: string;
  ai_content: string;
  created_at: string;
  is_shared: boolean;
  is_solved: boolean;
  like_count: number;
  comment_count: number;
  user_info: UserInfo;
}

export type SortOption = 'latest' | 'oldest' | 'comments' | 'likes';
// api/posts.ts
import axios from 'axios';
import { Post, SortOption } from '../types/post';

const BASE_URL = 'https://calmiary-be.org';

interface FetchPostsParams {
  pageParam?: number;
  sortBy?: SortOption;
}

export const fetchPosts = async ({ pageParam = 1, sortBy = 'latest' }: FetchPostsParams) => {
  const response = await axios.get<Post[]>(`${BASE_URL}/posts`, {
    params: {
      page: pageParam,
      limit: 3,
      sort_by: sortBy,
    },
  });
  return response.data;
};
// components/PostList.tsx
import React from 'react';
import { useInfiniteQuery } from '@tanstack/react-query';
import { useInView } from 'react-intersection-observer';
import { Post, SortOption } from '../types/post';
import { fetchPosts } from '../api/posts';

interface PostListProps {
  sortBy: SortOption;
}

export const PostList = ({ sortBy } : PostListProps) => {
  // ref: 관찰할 요소에 붙일 참조
  // inView: 해당 요소가 뷰포트에 보이는지 여부
  const { ref, inView } = useInView(); 

  const {
    data, // 페이지별 데이터를 포함하는 객체
    isLoading, // 로딩 상태
    isError, // 에러 발생 여부
    hasNextPage, // 다음 페이지 존재 여부
    fetchNextPage, // 다음 페이지 데이터를 불러오는 함수
    isFetchingNextPage,  // 다음 페이지 로딩 상태
  } = useInfiniteQuery({
    queryKey: ['posts', sortBy], // queryKey는 적절하게 변경

    // 실제 데이터를 가져오는 함수
    queryFn: ({ pageParam = 1 }) => fetchPosts({ pageParam, sortBy }), 

   // 다음 페이지 파라미터를 결정하는 함수
    getNextPageParam: (lastPage, allPages) => {

      // 마지막 페이지가 3개의 항목을 가지고 있으면 다음 페이지가 있다고 판단 가능
      return lastPage.length === 3 ? allPages.length + 1 : undefined;
    },

    // 초기 페이지
    initialPageParam: 1,
  });

  useEffect(() => {
    // 관찰 대상이 화면에 보이고 다음 페이지가 있다면
    if (inView && hasNextPage) {
      fetchNextPage(); // 다음 페이지 로드
    }
  }, [inView, hasNextPage, fetchNextPage]);

  if (isLoading) return <div>Loading...</div>;
  if (isError) return <div>Error loading posts</div>;

  return (
    <div className="post-list">
      {data.pages.map((group, i) => (
        <Fragment key={i}>
          {group.map((post: Post) => (
            <div key={post.id} className="post-card">
              <h3>{post.content}</h3>
              <div className="post-meta">
                <span>좋아요: {post.like_count}</span>
                <span>댓글: {post.comment_count}</span>
              </div>
              <div className="user-info">
                <img src={post.user_info.profile_image} alt="Profile" />
                <span>{post.user_info.nickname}</span>
              </div>
            </div>
          ))}
        </Fragment>
      ))}

      // 여기가 intersection observer에서 관찰하는 부분..!
      <div ref={ref} style={{ height: '20px' }}>
        {isFetchingNextPage && <div>Loading more...</div>}
      </div>
    </div>
  );
};

@zelkovaria @kod0751

zelkovaria commented 7 hours ago

useEffect 부분은 좀 더 비교해서 학습해볼게용 ㅎㅎ 감사합니당!