게시글, 해시태그 검색, useInView 사용

개발공부·2023년 2월 1일
0

* 결과(전체 게시글도 같은 방식)

1) lastId로 검색

2) 12개의 검색 결과 중 10개 검색 결과 호출 후 2개 호출

3) 해시태그 누름 -> 해시태그 12개 결과 중 10개 결과 호출 -> 2개 호출

* 중요한 점

▶ 모든 값을 찾고 스크롤 할 때마다 10개씩 추가함
▶ 어떤 값을 기준? -> 맨 마지막 포스트의 id값을 lastId로 만듦

const lastId = mainPosts[mainPosts.length - 1]?.id;

▶ useInView 사용

npm install react-intersection-observer
import { useInView } from "react-intersection-observer";

* 진행 순서

context.store.dispatch(loadPostsRequest());  
					=>
<div ref={hasMorePosts && !loadPostsLoading ? ref : undefined} className="h-10"/> 
					=>
useEffect(() => { 
  	if (inView && hasMorePosts && !loadPostsLoading) {
      const lastId = mainPosts[mainPosts.length - 1]?.id;
      dispatch(loadPostsRequest(lastId));
    }
  }, [inView, hasMorePosts, loadPostsLoading, mainPosts]);

* 참고

* useInView 참고한 글
* Infinity Scroll 적용하기 참고 글(블로그)

* 코드

[pages/post/index.js]

▶ 검색은 search/[content].js로 만들고, 해시태그는 hashtag/[tag].js로 만듦
▶ 각각 router.query 이용해 전달

const { content } = router.query; //검색
const { tag } = router.query; //해시태그

▶ API로 전달할 때 형식

dispatch(loadSearchPostsRequest({ lastId: lastId, data: content })); //검색
dispatch(loadHashtagPostsRequest({ lastId: lastId, data: tag }));// 해시태그

import axios from "axios";
import { useRouter } from "next/router";
import React, { useCallback, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { END } from "redux-saga";
import { useInView } from "react-intersection-observer";

import { loadPostsRequest } from "../../redux/feature/postSlice";
import { loadMyInfoRequest } from "../../redux/feature/userSlice";
import { loadWordsWeekendRequest } from "../../redux/feature/wordSlice";
import wrapper from "../../redux/store"; //ssr 사용하기 위함

const Index = () => {
  const dispatch = useDispatch();
  const router = useRouter();
  const { me } = useSelector((state) => state.user);
  const {
    mainPosts,
    loadPostsLoading,
    hasMorePosts,
    retweetError,
    likePostError,
    bookmarkError,
  } = useSelector((state) => state.post);
  const [ref, inView] = useInView(); //무한 스크롤 사용하기 위함

  useEffect(() => { //10개 지난 후 inView가 다시 loadPostsRequest를 호출함
  	if (inView && hasMorePosts && !loadPostsLoading) {
      const lastId = mainPosts[mainPosts.length - 1]?.id;
      dispatch(loadPostsRequest(lastId));
    }
  }, [inView, hasMorePosts, loadPostsLoading, mainPosts]);

  return (
    <>
              {mainPosts.map((post, index) => {
                return (
                  <PostCard key={index} post={post} index={index} me={me} />
                );
              })}
              <div
                ref={hasMorePosts && !loadPostsLoading ? ref : undefined}
                className="h-10"
              /> //10개 호출 후 다시 호출
            </div>
          </div>
        </div>
      </NavbarForm>
    </>
  );
};

export const getServerSideProps = wrapper.getServerSideProps(
  async (context) => {
    const cookie = context.req ? context.req.headers.cookie : "";
    axios.defaults.headers.Cookie = "";
    if (context.req && cookie) {
      axios.defaults.headers.Cookie = cookie;
    }

    context.store.dispatch(loadMyInfoRequest()); 
    context.store.dispatch(loadPostsRequest()); 
    context.store.dispatch(END);
    await context.store.sagaTask.toPromise();
  }
);

export default Index;

* [sagas/postSaga.js]

//전체 게시글
function loadPostsAPI(data) {
  return axios.get(`/posts?lastId=${data || 0}`); //data로 넘어갔으므로 req.query.data?
}

function* loadPosts(action) {
  try {
    const data = action.payload;
    const result = yield call(loadPostsAPI, data);
    yield put(loadPostsSuccess(result.data));
  } catch (error) {
    yield put(loadPostsFailure(error));
    console.log(error);
  }
}

//검색하기
function loadSearchPostsAPI(data, lastId) {
  return axios.get(
    `/posts/search/${encodeURIComponent(data)}?lastId=${lastId || 0}`
  ); //data로 넘어갔으므로 req.query.data?
}

function* loadSearchPosts(action) {
  try {
    const data = action.payload.data;
    const lastId = action.payload?.lastId;
    const result = yield call(loadSearchPostsAPI, data, lastId);
    yield put(loadSearchPostsSuccess(result.data));
  } catch (error) {
    yield put(loadSearchPostsFailure(error));
    console.log(error);
  }
}

//해시태그 검색
function loadHashtagPostsAPI(data, lastId) {
  return axios.get(
    `/hashtag/${encodeURIComponent(data)}?lastId=${lastId || 0}`
  ); //data로 넘어갔으므로 req.query.data?
}

function* loadHashtagPosts(action) {
  try {
    const data = action.payload.data;
    const lastId = action.payload?.lastId;
    const result = yield call(loadHashtagPostsAPI, data, lastId);
    yield put(loadHashtagPostsSuccess(result.data));
  } catch (error) {
    yield put(loadHashtagPostsFailure(error));
    console.log(error);
  }
}

* [postSlice.js]

//검색, 해시태그도 동일
 loadPostsSuccess: (state, action) => {
      const data = action.payload;
      state.loadPostsLoading = false;
      state.loadPostsComplete = true;
      state.mainPosts = state.mainPosts.concat(data);
      state.hasMorePosts = data.length === 10;
    },

* [routes/posts.js]

const express = require("express");
const { Op } = require("sequelize");
const { Post, Image, User, Comment, Hashtag } = require("../models");

const router = express.Router();

//전체 게시글
router.get("/", async (req, res, next) => {
  try {
    const where = {};
    if (parseInt(req.query.lastId, 10)) {
      // 초기 로딩이 아닐 때
      where.id = { [Op.lt]: parseInt(req.query.lastId, 10) };
    } //// 10 -> 10 + 10 -> 10 + 10 + 10 누적됨

    const posts = await Post.findAll({
      //모든 게시글 가져옴
      where,
      limit: 10,
      order: [
        ["createdAt", "DESC"], //최신 게시글부터
        [Comment, "createdAt", "DESC"],
      ],
      include: [
        {
          model: User,
          attributes: ["id", "nickname", "profileImg"],
        },
        {
          model: Image,
        },
        {
          model: Comment,
          include: [
            {
              model: User,
              attributes: ["id", "nickname", "profileImg"],
            },
          ],
        },
        {
          model: User, //좋아요 누른 사람
          as: "Likers", //post.Likers 생성
          attributes: ["id"],
        },
        {
          model: User, //좋아요 누른 사람
          as: "Bookmarks", //post.Bookmarks 생성
          attributes: ["id"],
        },
        {
          model: Post,
          as: "Retweet",
          include: [
            {
              model: User,
              attributes: ["id", "nickname", "profileImg"],
            },
            {
              model: Image,
            },
          ],
        },
      ],
    });
    res.status(200).json(posts);
  } catch (error) {
    console.error(error);
    next(error);
  }
});

//검색 게시글
router.get("/search/:content", async (req, res, next) => {
  // GET /posts/search/노드
  try {
    const content = req.params.content;
    const where = {
      content: {
        [Op.like]: `%${decodeURIComponent(content)}%`,
      },
    };
    if (parseInt(req.query.lastId, 10)) {
      // 초기 로딩이 아닐 때
      where.id = { [Op.lt]: parseInt(req.query.lastId, 10) };
    } // 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

    const posts = await Post.findAll({
      //모든 게시글 가져옴
      where,
      limit: 10,
      order: [
        ["createdAt", "DESC"], //최신 게시글부터
        [Comment, "createdAt", "DESC"],
      ],
      include: [
       포함하고자 하는 내용(전체 게시글과 동일)
      ],
    });
    res.status(200).json(posts);
  } catch (error) {
    console.error(error);
    next(error);
  }
});

[routes/hashtag.js]

const express = require("express");
const router = express.Router();
const { Hashtag, Post, Image, Comment, User } = require("../models");
const { Op } = require("sequelize");

router.get("/:hashtag", async (req, res, next) => {
  // GET /hashtag/노드
  try {
    const hashtag = req.params.hashtag;
    const where = {
      content: {
        [Op.like]: `%#${decodeURIComponent(hashtag)}%`,
      },
    };
    if (parseInt(req.query.lastId, 10)) {
      // 초기 로딩이 아닐때
      where.id = { [Op.lt]: parseInt(req.query.lastId, 10) };
    }
    const posts = await Post.findAll({
      where,
      limit: 10,
      order: [["createdAt", "DESC"]],
       include: [
       포함하고자 하는 내용(전체 게시글과 동일)
      ],
    });
    res.status(200).json(posts);
  } catch (error) {
    console.error(error);
    next(error);
  }
});
profile
개발 블로그, 티스토리(https://ba-gotocode131.tistory.com/)로 갈아탐

0개의 댓글