React + GraphQL - Typescript 무한스크롤 만들기

HOONEY·2022년 4월 18일
0

React

목록 보기
5/5
post-thumbnail

무한스크롤을 만들어보려고 한다.

랜딩페이지

  • Router.tsx에 무한스크롤 구현할 페이지를 만들어준다.
import React from "react";
import { BrowserRouter , Route, Routes } from "react-router-dom";
import Navigation from "./components/Navigation";
import Home from "./pages/Home";
import Modify from "./pages/Modify";
import SignUp from "./pages/SignUp";
import Study from "./pages/Study";
import InfinityScroll from "./pages/InfinityScroll";

function Router() {
  return (
    <BrowserRouter >
      <Navigation />
      <br />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/signup" element={<SignUp />} />
        <Route path="/modify" element={<Modify />} />
        <Route path="/study" element={<Study />} />
        <Route path="/InfinityScroll" element={<InfinityScroll />} />
      </Routes>
    </BrowserRouter >
  );
}

export default Router;
  • Navigation.tsx에 무한스크롤 페이지로 갈 수 있도록 링크도 추가해준다.
import React from "react";
import { Link } from "react-router-dom";
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { useCookies } from "react-cookie";

function Navigation() {
  const [cookies] = useCookies(['test']);
  return (
    <div className="nav">
      <Box sx={{ display: 'flex', alignItems: 'center', textAlign: 'center' }}>
        <Button>
          <Link to="/" style={{ textDecoration: 'none', color: '#1976d2' }}>Home</Link>
        </Button>
        <Button style={cookies.test === undefined ? {display: 'none'} : {display: ''}}>
          <Link to="/modify" style={{ textDecoration: 'none', color: '#1976d2' }}>Modify</Link>
        </Button>
        <Button>
          <Link to="/study" style={{ textDecoration: 'none', color: '#1976d2' }}>Study</Link>
        </Button>
        <Button>
          <Link to="/InfinityScroll" style={{ textDecoration: 'none', color: '#1976d2' }}>InfinityScroll</Link>
        </Button>
      </Box>
    </div>
  );
}

export default Navigation;
  • 무한스크롤 페이지에 오면 먼저 20개의 데이터를 보여준다.
  • getUsersInit함수가 useEffect를 통해 화면 시작 시에만 실행되어 데이터 20건을 가져온다.
  • useCallbak으로 스크롤 위치를 감지하여 스크롤이 거의 끝에 왔을 경우에 getUsers 함수로 그 다음 20건을 가져온다.
  • 특정 스크롤 에서 함수가 실행되지만, if문으로 다음에 가져올 데이터가 있을 경우에만 가져올 수 있도록 isExistMore을 검사해서 실행한다.
  • 데이터를 20건씩 가져오는데, 20건보다 적을 경우 더이상 가져올 데이터가 없으므로 그럴때 false값으로 변경되게끔 한다.
import React, { useCallback, useEffect, useState } from "react";
import { GET_USERS_INIT, GET_USERS } from "../gql/InfinityScroll.gql";
import { useLazyQuery } from "@apollo/client";
import UserList from "../components/UserList";

function InfinityScroll() {
  const [users, setUsers] = useState<any>([]);
  const [lastId, setLastId] = useState("");
  const [isUsersLoading, setIsUsersLoading] = useState<boolean>(false);
  const [isExistMore, setIsExistMore] = useState<boolean>(true);
  const [getUsersInit, {loading, error}] = useLazyQuery(GET_USERS_INIT, {
    fetchPolicy: "cache-and-network",
    onError: error => {
      console.error(error);
      alert(error.message);
    },
    onCompleted: ({getUsersInit}) => {
      setUsers(getUsersInit);
      setLastId(getUsersInit[getUsersInit.length - 1]._id);
    }
  });
  const [getUsers, {loading: loading2, error: error2}] = useLazyQuery(GET_USERS, {
    fetchPolicy: "cache-and-network",
    onError: error => {
      console.error(JSON.stringify(error, null, 2))
      alert(error.message);
    },
    onCompleted: ({getUsers}) => {
      if (getUsers.length < 20) setIsExistMore(false);
      setIsUsersLoading(false);

      setLastId(getUsers[getUsers.length - 1]._id);
      setUsers([...users, ...getUsers]);
    }
  });

  const handleScroll = useCallback((): void => {
    const { innerHeight } = window;
    // 브라우저창 내용의 크기 (스크롤을 포함하지 않음)
    
    const { scrollHeight } = document.body;
    // 브라우저 총 내용의 크기 (스크롤을 포함한다)
    
    const { scrollTop } = document.documentElement;
    // 현재 스크롤바의 위치
    
    if (Math.round(scrollTop + innerHeight) >= scrollHeight) {
      // scrollTop과 innerHeight를 더한 값이 scrollHeight보다 크다면, 가장 아래에 도달했다는 의미이다.
      if (isExistMore) {
        setIsUsersLoading(true);
        getUsers({ variables: { lastId } });
      } 

    }
  }, [lastId]);

  useEffect(() => {
    getUsersInit()
  }, []);

  useEffect(() => {
    window.addEventListener('scroll', handleScroll, true);
    // 스크롤이 발생할때마다 handleScroll 함수를 호출하도록 추가합니다.
    
    return () => {
      window.removeEventListener('scroll', handleScroll, true);
      // 해당 컴포넌트가 언마운트 될때, 스크롤 이벤트를 제거합니다.
    };
  }, [handleScroll]);

  return (
    <div>
      <UserList userData={users} />
    </div>
  );
}

export default InfinityScroll;
  • 데이터의 구성은 _id, email, name인데, _id는 mongodb가 자동으로 만들어주는 난수 값으로 _id 안에 timestamp값이 들어있기 때문에 데이터 20건 중 마지막 값을 서버에 넘겨줌으로 다음 20건을 가져올 수 있도록 한다.

  • UserList 작성

import React from "react";
import UserInfo from "./UserInfo";

function UserList({userData}: any) {
    return (
        <div>
            {userData.map((user: any) => (
                <UserInfo 
                  key={user._id}
                  _id={user._id}
                  email={user.email}
                  name={user.name}
                />
            ))}
        </div>
    )
}

export default UserList;
  • UserInfo 작성
import React from "react";

function UserInfo({_id, email, name}: any) {
    return (
        <div>
            <div style={{border: "1px solid grey", borderRadius: 10, padding: 10, margin: 10}}>
                <p>_id: {_id}</p>
                <p>email: {email}</p>
                <p>name: {name}</p>
            </div>
        </div>
    )
}

export default UserInfo;
  • InfinityScroll.gql.ts 작성
import gql from 'graphql-tag';

export const GET_USERS_INIT = gql`
    query getUsersInit {
        getUsersInit {
            _id
            email
            name
        }
    }
`

export const GET_USERS = gql`
    query getUsers($lastId: String!) {
        getUsers(lastId: $lastId) {
            _id
            email
            name
        }
    }
`

구현 결과

서버 코드 작성...

IntersectionObserver 공부하기...

profile
기록하는 블로그

0개의 댓글