Skip to content

Commit 38113fc

Browse files
authored
Merge(feature): 뒤로 가기 시 스크롤 위치 복원
feature: 뒤로 가기 시 스크롤 위치 복원
2 parents 7f7a41b + b8e83da commit 38113fc

11 files changed

Lines changed: 115 additions & 93 deletions

File tree

src/hooks/useScrollPosition.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import useSessionStorage from './useSessionStorage';
2+
3+
const SCROLL_POSITION_KEY = 'prevPostIndex';
4+
5+
export default () => useSessionStorage(SCROLL_POSITION_KEY, 0);

src/hooks/useScrollToTop.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { useEffect } from 'react';
2+
import { useLocation } from 'react-router-dom';
3+
4+
const useScrollToTop = () => {
5+
const { pathname } = useLocation();
6+
7+
useEffect(() => {
8+
window.scrollTo(0, 0);
9+
}, [pathname]);
10+
11+
return null;
12+
};
13+
14+
export default useScrollToTop;

src/hooks/useSessionStorage.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useState } from 'react';
2+
3+
const useSessionStorage = (key, initialValue) => {
4+
const [storedValue, setStoredValue] = useState(() => {
5+
try {
6+
const item = sessionStorage.getItem(key);
7+
return item ? JSON.parse(item) : initialValue;
8+
} catch (error) {
9+
console.error(error);
10+
return initialValue;
11+
}
12+
});
13+
14+
const setValue = (value) => {
15+
try {
16+
setStoredValue(value);
17+
sessionStorage.setItem(key, JSON.stringify(value));
18+
} catch (error) {
19+
console.error(error);
20+
}
21+
};
22+
23+
return [storedValue, setValue];
24+
};
25+
26+
export default useSessionStorage;

src/pages/MainPage/PostBody.jsx

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,52 @@ import theme from 'styles/theme';
88
import { setLike, setDisLike } from 'utils/apis/postApi';
99
import { setNotification } from 'utils/apis/userApi';
1010
import useLocalToken from 'hooks/useLocalToken';
11+
import useScrollPosition from 'hooks/useScrollPosition';
1112
import { useUserContext } from 'contexts/UserContext';
1213
import { IMAGE_URLS } from 'utils/constants/images';
1314
import displayedAt from 'utils/functions/displayedAt';
1415

15-
const PostBody = ({ post, isDetailPage = false }) => {
16+
const PostBody = ({ index, post, isDetailPage = false }) => {
1617
const { _id: postId, image, likes, comments, createdAt, author } = post || {};
17-
const { content, contents, tags } = JSON.parse(post?.title);
18+
const { content, tags } = JSON.parse(post?.title);
1819
const [onHeart, setOnHeart] = useState(false);
1920
const [heartCount, setHeartCount] = useState(likes.length);
2021
const [likeId, setLikeId] = useState('');
2122
const [isShown, setIsShown] = useState(false);
2223
const [localToken] = useLocalToken();
2324
const { currentUser } = useUserContext();
24-
25+
const [, setCurrentPostIndex] = useScrollPosition();
2526
const navigate = useNavigate();
2627

27-
const handleTodetailpage = useCallback(() => {
28+
useEffect(() => {
29+
let likeId;
30+
const isMyLikePost =
31+
likes.filter(({ user, _id }) => {
32+
if (user === currentUser.id) {
33+
likeId = _id;
34+
return true;
35+
}
36+
}).length > 0;
37+
if (isMyLikePost) {
38+
setOnHeart(true);
39+
setLikeId(likeId);
40+
}
41+
}, [currentUser, likes]);
42+
43+
const navigateToDetailPage = useCallback(() => {
2844
if (isDetailPage) {
2945
return;
3046
}
47+
setCurrentPostIndex(index + 1);
3148
navigate(`/post/detail/${postId}`, {
3249
state: {
3350
post,
51+
index,
3452
},
3553
});
36-
}, [postId, post, isDetailPage, navigate]);
54+
}, [setCurrentPostIndex, index, postId, post, isDetailPage, navigate]);
3755

38-
const handleTagClick = useCallback(
39-
(tag) => {
40-
navigate(`/tag/${tag.slice(1)}`, {
41-
state: {
42-
tag,
43-
},
44-
});
45-
},
46-
[navigate],
47-
);
48-
49-
const handleHeartClick = useCallback(async () => {
56+
const handleClickHeart = useCallback(async () => {
5057
setOnHeart(!onHeart);
5158
if (!onHeart) {
5259
setHeartCount(heartCount + 1);
@@ -66,28 +73,25 @@ const PostBody = ({ post, isDetailPage = false }) => {
6673
}
6774
}, [onHeart, heartCount, postId, likeId, author._id, currentUser, localToken]);
6875

69-
useEffect(() => {
70-
let likeId;
71-
const isMyLikePost =
72-
likes.filter(({ user, _id }) => {
73-
if (user === currentUser.id) {
74-
likeId = _id;
75-
return true;
76-
}
77-
}).length > 0;
78-
if (isMyLikePost) {
79-
setOnHeart(true);
80-
setLikeId(likeId);
81-
}
82-
}, [currentUser, likes]);
76+
const handleClickTag = useCallback(
77+
(tag) => {
78+
setCurrentPostIndex(index + 1);
79+
navigate(`/tag/${tag.slice(1)}`, {
80+
state: {
81+
tag,
82+
},
83+
});
84+
},
85+
[index, setCurrentPostIndex, navigate],
86+
);
8387

84-
const handleMoreClick = () => {
88+
const handleClickMore = () => {
8589
setIsShown(true);
8690
};
8791

8892
return (
8993
<Container>
90-
<ImageWrapper onClick={handleTodetailpage} isDetailPage={isDetailPage}>
94+
<ImageWrapper onClick={navigateToDetailPage} isDetailPage={isDetailPage}>
9195
<Image
9296
src={image ? image : IMAGE_URLS.POST_DEFAULT_IMG}
9397
defaultImageUrl={IMAGE_URLS.POST_DEFAULT_IMG}
@@ -98,13 +102,12 @@ const PostBody = ({ post, isDetailPage = false }) => {
98102
<Contents>
99103
<IconButtons>
100104
<IconButton
101-
className="heart-button"
102105
name={onHeart ? 'HEART_RED' : 'HEART'} // Todo: 상수화
103-
onClick={handleHeartClick}
106+
onClick={handleClickHeart}
104107
>
105108
<IconButtonText>{heartCount}</IconButtonText>
106109
</IconButton>
107-
<IconButton className="comment-button" name="COMMENT" onClick={handleTodetailpage}>
110+
<IconButton name="COMMENT" onClick={navigateToDetailPage}>
108111
<IconButtonText>{comments.length}</IconButtonText>
109112
</IconButton>
110113
</IconButtons>
@@ -113,12 +116,12 @@ const PostBody = ({ post, isDetailPage = false }) => {
113116
{content}
114117
</Paragraph>
115118
{!isDetailPage && content?.length > 50 && !isShown && (
116-
<MoreText onClick={handleMoreClick}>더보기</MoreText>
119+
<MoreText onClick={handleClickMore}>더보기</MoreText>
117120
)}
118121
</TextContainer>
119122
<Tags>
120123
{tags.map((tag, i) => (
121-
<Tag key={i} onClick={() => handleTagClick(tag)}>
124+
<Tag key={i} onClick={() => handleClickTag(tag)}>
122125
{tag[0] === '#' ? tag : `#${tag}`}
123126
</Tag>
124127
))}
@@ -155,7 +158,7 @@ const IconButtons = styled.div`
155158
display: flex;
156159
`;
157160

158-
const IconButton = ({ name, className, children, onClick }) => {
161+
const IconButton = ({ name, children, onClick }) => {
159162
const style = {
160163
padding: 0,
161164
borderRadius: '0',
@@ -167,7 +170,7 @@ const IconButton = ({ name, className, children, onClick }) => {
167170
color: theme.color.fontBlack,
168171
};
169172
return (
170-
<button className={className} style={style} onClick={onClick}>
173+
<button style={style} onClick={onClick}>
171174
<Icon name={name} size={22} />
172175
{children}
173176
</button>

src/pages/MainPage/PostItem.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import PostBody from './PostBody';
33
import styled from '@emotion/styled';
44
import theme from 'styles/theme';
55

6-
const PostItem = ({ post, isDetailPage }) => {
6+
const PostItem = ({ index, post, isDetailPage }) => {
77
return (
88
<Article>
99
<PostHeader post={post} isDetailPage={isDetailPage} />
10-
<PostBody post={post} isDetailPage={isDetailPage} />
10+
<PostBody index={index} post={post} isDetailPage={isDetailPage} />
1111
</Article>
1212
);
1313
};

src/pages/MainPage/index.jsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,60 @@ import { PageWrapper } from 'components';
33
import { getPostsPart } from 'utils/apis/postApi';
44
import PostItem from './PostItem';
55
import { useState, useEffect, useCallback, useRef } from 'react';
6+
import useScrollPosition from 'hooks/useScrollPosition';
67

78
const LIMIT = 5;
89

910
const MainPage = () => {
1011
const [posts, setPosts] = useState([]);
11-
const [isLoding, setIsLoding] = useState(false);
12+
const [isLoading, setIsLoading] = useState(false);
1213
const [offset, setOffset] = useState(0);
1314
const [max, setMax] = useState(0);
1415
const targetRef = useRef(null);
16+
const [prevPostIndex, setPrevPostIndex] = useScrollPosition();
1517

1618
useEffect(() => {
19+
const limit = prevPostIndex ? prevPostIndex : LIMIT;
1720
(async () => {
1821
const nextPosts = await getPostsPart({
1922
offset,
20-
limit: LIMIT,
23+
limit,
2124
}).then((res) => res.data);
2225
setPosts(nextPosts);
23-
setOffset(offset + 5);
2426
setMax(nextPosts[0].channel.posts.length);
27+
setOffset(prevPostIndex ? prevPostIndex : 5);
2528
})();
2629
}, []);
2730

31+
useEffect(() => {
32+
if (targetRef.current && prevPostIndex) {
33+
window.scrollTo(0, document.body.scrollHeight);
34+
setPrevPostIndex(0);
35+
}
36+
}, [targetRef, prevPostIndex, setPrevPostIndex]);
37+
2838
const onIntersect = useCallback(
2939
async ([entry], observer) => {
30-
if (entry.isIntersecting && !isLoding && offset < max) {
40+
if (entry.isIntersecting && !isLoading && offset < max) {
3141
observer.disconnect();
32-
setIsLoding(true);
42+
setIsLoading(true);
3343
setOffset(offset + 5);
3444
const nextPosts = await getPostsPart({
3545
offset,
3646
limit: LIMIT,
3747
}).then((res) => res.data);
3848
setPosts([...posts, ...nextPosts]);
39-
setIsLoding(false);
49+
setIsLoading(false);
4050
}
4151
},
42-
[isLoding, offset, max, posts],
52+
[isLoading, offset, max, posts],
4353
);
4454

4555
useEffect(() => {
4656
let observer;
4757
if (targetRef.current) {
4858
observer = new IntersectionObserver(onIntersect, {
49-
threshold: 0.4,
59+
threshold: 0.5,
5060
});
5161
observer.observe(targetRef.current);
5262
}
@@ -60,13 +70,13 @@ const MainPage = () => {
6070
if (posts.length - 1 === i) {
6171
return (
6272
<li key={i} ref={targetRef}>
63-
<PostItem key={i} post={post} />
73+
<PostItem key={i} index={i} post={post} />
6474
</li>
6575
);
6676
} else {
6777
return (
6878
<li key={i}>
69-
<PostItem key={i} post={post} />
79+
<PostItem key={i} index={i} post={post} />
7080
</li>
7181
);
7282
}

src/pages/PostDetailPage/index.jsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Avatar, Icon, Modal, PageWrapper } from 'components';
44
import theme from 'styles/theme';
55
import { useRef, useCallback, useState, useEffect } from 'react';
66
import { addComment } from 'utils/apis/postApi';
7-
import { IMAGE_URLS } from 'utils/constants/images';
87
import useLocalToken from 'hooks/useLocalToken';
98
import { useLocation, useNavigate } from 'react-router-dom';
109
import { getPostData, deleteComment } from 'utils/apis/postApi';
@@ -97,7 +96,7 @@ const PostDetailPage = () => {
9796
{post && (
9897
<PageWrapper header nav prev title={post.author.fullName + '님의 게시물'}>
9998
<Container>
100-
<PostItem post={post} isDetailPage={true} />
99+
<PostItem post={post} isDetailPage={true} index={location.state?.index} />
101100
<CommentInputForm onSubmit={handleSubmit}>
102101
<CommentInput
103102
ref={inputRef}

src/routes/Router.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useEffect } from 'react';
22
import useLocalToken from 'hooks/useLocalToken';
33
import { Routes, Route, useNavigate } from 'react-router-dom';
44
import { useUserContext } from 'contexts/UserContext';
5+
import useScrollToTop from 'hooks/useScrollToTop';
56
import {
67
LoginPage,
78
SignupPage,
@@ -22,6 +23,7 @@ const Router = () => {
2223
const navigate = useNavigate();
2324
const [token] = useLocalToken();
2425
const { onKeepLoggedIn } = useUserContext();
26+
useScrollToTop();
2527

2628
useEffect(() => {
2729
if (!token) {

src/template/DefaultTemplate.jsx

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,8 @@
11
import styled from '@emotion/styled';
2-
import { useEffect, useState } from 'react';
32

43
const DefaultTemplate = ({ children }) => {
5-
const [height, setHeight] = useState(window.innerHeight);
6-
7-
const handleResize = () => {
8-
setHeight(window.innerHeight);
9-
};
10-
11-
useEffect(() => {
12-
window.addEventListener('resize', handleResize);
13-
return () => window.removeEventListener('resize', handleResize);
14-
});
15-
164
return (
17-
<Container id="default-template-container" height={height}>
5+
<Container id="default-template-container">
186
<StyledMain>{children}</StyledMain>
197
</Container>
208
);
@@ -29,11 +17,10 @@ const Container = styled.div`
2917
flex-direction: column;
3018
position: relative;
3119
width: 500px;
20+
min-height: 100vh;
3221
overflow-x: hidden;
3322
margin: 0 auto;
34-
height: ${({ height }) => `${height}`}px;
3523
background-color: ${({ theme }) => theme.color.mainWhite};
36-
3724
-ms-overflow-style: none;
3825
3926
::-webkit-scrollbar {

0 commit comments

Comments
 (0)