▶ 모든 값을 찾고 스크롤 할 때마다 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 적용하기 참고 글(블로그)
▶ 검색은 search/[content].js로 만들고, 해시태그는 hashtag/[tag].js로 만듦
▶ 각각 router.query 이용해 전달
const { content } = router.query; //검색
const { tag } = router.query; //해시태그
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;
//전체 게시글
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);
}
}
//검색, 해시태그도 동일
loadPostsSuccess: (state, action) => {
const data = action.payload;
state.loadPostsLoading = false;
state.loadPostsComplete = true;
state.mainPosts = state.mainPosts.concat(data);
state.hasMorePosts = data.length === 10;
},
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);
}
});
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);
}
});