나의 구독 페이지 위주로 설명하겠다.
필요한 함수는 4개이다.
내가 구독하고 있는 사용자 목록을 받아오는 함수. 매개변수로 내 homeId를 전달한다.
export const getFollowingProfile = async (homeId: string | undefined): Promise<SimpleProfileListType | false> => {
try {
const res = await fetchApi.get(`/api/member/${homeId}/following`);
if (res.status !== 200) throw new Error('error');
const { follows } = await res.json();
return follows;
} catch (err) {
return false;
}
};
나를 구독하고 있는 사용자 목록을 받아오는 함수. 마찬가지로 매개변수로 내 homeId를 전달한다.
export const getFollowerProfile = async (homeId: string): Promise<SimpleProfileListType | false> => {
try {
const res = await fetchApi.get(`/api/member/${homeId}/follower`);
if (res.status !== 200) throw new Error('error');
const { follows } = await res.json();
return follows;
} catch (err) {
return false;
}
};
구독 목록 추가 함수. 매개변수로 내 homeId와 구독할 사용자의 정보 목록을 전달한다.
// 리스트 추가 요청
export const updateFollowing = async (
homeId: string,
updateFollowingReqType: UpdateFollowingType,
): Promise<UpdateFollowingType | false> => {
try {
const res = await fetchApi.put(`/api/member/${homeId}/following`, updateFollowingReqType);
if (res.status !== 200) throw new Error('error');
return await res.json();
} catch (err) {
return false;
}
};
구독 목록 삭제 함수. 매개변수로 내 homeId와 구독 취소할 사용자의 homeId를 전달한다.
// 리스트 삭제 요청
export const deleteFollowing = async (homeId: string, targetHomeId: string | undefined): Promise<boolean> => {
try {
const res = await fetchApi.delete(`/api/member/${homeId}/following/${targetHomeId}`);
if (res.status !== 200) throw new Error('error');
return true;
} catch (err) {
return false;
}
};
구독 페이지가 팝업으로 되어있어 최상위 컴포넌트명이 FollowModal이다.
import React, { useState, useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import ModalFrame from '@components/Modal/ModalFrame';
import FollowTab from '@components/Common/Tab/FollowTab';
import FollowList from '@components/FollowList';
import SimpleProfile from '@src/components/Common/SimpleProfile';
import { SimpleProfileListType } from '@src/types/member';
import { userProfileState } from '@stores/user';
import { getFollowingProfile, getFollowerProfile } from '@apis/follow';
import { Container } from './style';
interface Props {
handleFollowModalClose: () => void;
}
const FollowModal: React.FC<Props> = ({ handleFollowModalClose }) => {
const [isFollowing, setIsFollowing] = useState(true);
const [followingProfiles, setFollowingProfiles] = useState<SimpleProfileListType>([]);
const [followerProfiles, setFollowerProfiles] = useState<SimpleProfileListType>([]);
const { homeId } = useRecoilValue(userProfileState);
const getFollowingProfiles = async () => {
const data = await getFollowingProfile(homeId);
if (data) {
setFollowingProfiles(data);
} else {
setFollowingProfiles([]);
}
};
const getFollowerProfiles = async () => {
const data = await getFollowerProfile(homeId);
if (data) {
setFollowerProfiles(data);
} else {
setFollowerProfiles([]);
}
};
const handleFirstLink = () => {
setIsFollowing(true);
};
const handleSecondLink = () => {
setIsFollowing(false);
};
useEffect(() => {
getFollowingProfiles();
getFollowerProfiles();
}, []);
useEffect(() => {
getFollowingProfiles();
getFollowerProfiles();
}, [isFollowing]);
return (
<ModalFrame w={43} h={48} handleModalClose={handleFollowModalClose}>
<Container>
<FollowTab
firstText="구독중"
secondText="구독자"
handleFirstLink={handleFirstLink}
handleSecondLink={handleSecondLink}
focus={isFollowing ? 'first' : 'second'}
/>
<FollowList>
{isFollowing
? followingProfiles.map((t) => (
<SimpleProfile
key={t.targetHomeId}
targetHomeId={t.targetHomeId}
memberImg={t.memberImg}
name={t.name}
bio={t.bio}
followingProfiles={followingProfiles}
/>
))
: followerProfiles.map((t) => (
<SimpleProfile
key={t.targetHomeId}
targetHomeId={t.targetHomeId}
memberImg={t.memberImg}
name={t.name}
bio={t.bio}
followingProfiles={followingProfiles}
/>
))}
</FollowList>
</Container>
</ModalFrame>
);
};
export default FollowModal;
const [isFollowing, setIsFollowing] = useState(true);
const [followingProfiles, setFollowingProfiles] = useState<SimpleProfileListType>([]);
const [followerProfiles, setFollowerProfiles] = useState<SimpleProfileListType>([]);
const { homeId } = useRecoilValue(userProfileState);
isFollowing : true면 구독중 탭, false면 구독자 탭으로 이동하기 위한 flag 변수
followingProfiles : 내가 구독하는 사용자 정보 목록을 저장하는 변수
followerProfiles : 나를 구독하는 사용자 정보 목록을 저장하는 변수
homeId : recoil을 사용하여 내 homeId를 저장해둔 전역변수
const getFollowingProfiles = async () => {
const data = await getFollowingProfile(homeId);
if (data) {
setFollowingProfiles(data);
} else {
setFollowingProfiles([]);
}
};
const getFollowerProfiles = async () => {
const data = await getFollowerProfile(homeId);
if (data) {
setFollowerProfiles(data);
} else {
setFollowerProfiles([]);
}
};
전역변수 homeId를 전달하여 구독중, 구독자 목록을 받아와 저장한다. 불러오기를 실패하면 빈 리스트로 저장한다.
const handleFirstLink = () => {
setIsFollowing(true);
};
const handleSecondLink = () => {
setIsFollowing(false);
};
구독중, 구독자 링크 이동을 위한 handler 함수
useEffect(() => {
getFollowingProfiles();
getFollowerProfiles();
}, []);
useEffect(() => {
getFollowingProfiles();
getFollowerProfiles();
}, [isFollowing]);
<FollowTab
firstText="구독중"
secondText="구독자"
handleFirstLink={handleFirstLink}
handleSecondLink={handleSecondLink}
focus={isFollowing ? 'first' : 'second'}
/>
구독중 혹은 구독자 페이지로 이동하는 탭이다.
<FollowList>
{isFollowing
? followingProfiles.map((t) => (
<SimpleProfile
key={t.targetHomeId}
homeId={homeId}
targetHomeId={t.targetHomeId}
memberImg={t.memberImg}
name={t.name}
bio={t.bio}
followingProfiles={followingProfiles}
/>
))
: followerProfiles.map((t) => (
<SimpleProfile
key={t.targetHomeId}
homeId={homeId}
targetHomeId={t.targetHomeId}
memberImg={t.memberImg}
name={t.name}
bio={t.bio}
followingProfiles={followingProfiles}
/>
))}
</FollowList>
isFollowing의 상태에 따라 구독중 혹은 구독자 목록을 불러온다. 불러온 정보는 SimpleProfile 컴포넌트에 하나씩 매핑된다. 추후 구독 버튼 기능을 위하여 내 homeId와 내가 구독하는 목록(followingProfiles)도 전달해준다.
매핑된 유저 정보와 구독하기 버튼을 포함한 컴포넌트다.
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import ProfileIcon from '@components/Common/Profile/ProfileIcon';
import FollowButton from '@components/Common/FollowButton';
import { updateFollowing, deleteFollowing } from '@src/apis/follow';
import { SimpleProfileListType } from '@src/types/member';
import { Container, ButtonWrapper } from './style';
interface Props {
targetHomeId: string;
homeId: string;
memberImg: string;
name: string;
bio: string;
followingProfiles: SimpleProfileListType;
}
const SimpleProfile: React.FC<Props> = ({ targetHomeId, homeId, memberImg, name, bio, followingProfiles }) => {
const [buttonText, setButtonText] = useState('...');
const changeText = () => {
if (followingProfiles) {
if (followingProfiles.find((f) => f.targetHomeId === targetHomeId)) {
setButtonText('구독 중');
} else {
setButtonText('구독하기');
}
}
};
const handleFollowButton = async () => {
if (buttonText === '구독 중') {
const result = await deleteFollowing(homeId, targetHomeId);
if (result) {
setButtonText('구독하기');
}
} else {
const result = await updateFollowing(homeId, {
targetHomeId,
name,
memberImg,
bio,
});
if (result) {
setButtonText('구독 중');
}
}
};
useEffect(() => {
changeText();
}, [followingProfiles]);
return (
<Container>
<Link to={`/@${targetHomeId}`}>
<ProfileIcon size={3.75} profileImg={memberImg} />
<div>
<b>{name}</b>
<span>{bio}</span>
</div>
</Link>
<ButtonWrapper>
<FollowButton text={buttonText} handler={handleFollowButton} />
</ButtonWrapper>
</Container>
);
};
export default SimpleProfile;
const [buttonText, setButtonText] = useState('...');
const changeText = () => {
if (followingProfiles) {
if (followingProfiles.find((f) => f.targetHomeId === targetHomeId)) {
setButtonText('구독 중');
} else {
setButtonText('구독하기');
}
}
};
const handleFollowButton = async () => {
if (buttonText === '구독 중') {
const result = await deleteFollowing(homeId, targetHomeId);
if (result) {
setButtonText('구독하기');
}
} else {
const result = await updateFollowing(homeId, {
targetHomeId,
name,
memberImg,
bio,
});
if (result) {
setButtonText('구독 중');
}
}
};
useEffect(() => {
changeText();
}, [followingProfiles]);
내가 구독중인 목록이 변경될 때(== deleteFollowing, updateFollowing api를 실행할 때 == 구독하기 버튼 핸들러가 실행될 때)마다 buttonText를 변경해준다.
<Container>
<Link to={`/@${targetHomeId}`}>
<ProfileIcon size={3.75} profileImg={memberImg} />
<div>
<b>{name}</b>
<span>{bio}</span>
</div>
</Link>
<ButtonWrapper>
<FollowButton text={buttonText} handler={handleFollowButton} />
</ButtonWrapper>
</Container>
.
.
.
const HomeTemplate: React.FC<Props> = ({
isMyHome,
homeId,
targetHomeId,
profile,
following,
tickets,
isLoaded,
ticketbooks,
initialTicketbookCount,
cloneTicketbooks,
setTarget,
handlePageNavigate,
changeCurrTicketbookId,
}) => {
const [buttonText, setButtonText] = useState('...');
const changeText = () => {
if (following.find((f) => f.targetHomeId === targetHomeId)) {
setButtonText('구독 중');
} else {
setButtonText('구독하기');
}
return buttonText;
};
const handleFollowButton = async () => {
if (buttonText === '구독 중') {
const result = await deleteFollowing(homeId, targetHomeId);
if (result) {
setButtonText('구독하기');
}
} else {
const result = await updateFollowing(homeId, {
targetHomeId,
name: profile.name,
memberImg: profile.img,
bio: profile.bio,
});
if (result) {
setButtonText('구독 중');
}
}
};
useEffect(() => {
if (buttonText === '...') {
changeText();
}
});
useEffect(() => {
changeText();
}, [following]);
return (
<Layout>
<ProfileWrapper>
<ProfileBox
img={profile.img}
name={profile.name}
bio={profile.bio}
ticketCount={profile.ticketCount}
likeCount={profile.likeCount}
/>
</ProfileWrapper>
<ButtonWrapper>
{isMyHome ? (
<BasicButton text="티켓추가" handler={handlePageNavigate} />
) : (
<FollowButton text={buttonText} handler={handleFollowButton} />
)}
</ButtonWrapper>
.
.
.
<HomeBackground />
</Layout>
);
};
export default HomeTemplate;
.
.
.
const PostTemplate: React.FC<Props> = ({ post, isMyHome, like, handlePostDelete, handlePostEdit, handleLike }) => {
const myProfile = useRecoilValue(userProfileState);
const sanitizer = dompurify.sanitize;
const [buttonText, setButtonText] = useState('...');
const [followingProfiles, setFollowingProfiles] = useState<SimpleProfileListType>([]);
const getFollowingProfiles = async () => {
const data = await getFollowingProfile(myProfile.homeId);
if (data) {
setFollowingProfiles(data);
} else {
setFollowingProfiles([]);
}
};
const changeText = () => {
if (followingProfiles) {
if (followingProfiles.find((f) => f.targetHomeId === post?.memberHomeId)) {
setButtonText('구독 중');
} else {
setButtonText('구독하기');
}
}
};
const handleFollowButton = async () => {
if (buttonText === '구독 중') {
const result = await deleteFollowing(myProfile.homeId, post?.memberHomeId);
if (result) {
setButtonText('구독하기');
}
} else {
const result = await updateFollowing(myProfile.homeId, {
targetHomeId: post?.memberHomeId,
name: post?.memberName,
memberImg: post?.memberImg,
bio: post?.memberBio,
});
if (result) {
setButtonText('구독 중');
}
}
};
useEffect(() => {
if (buttonText === '...') {
changeText();
}
});
useEffect(() => {
getFollowingProfiles();
}, []);
useEffect(() => {
changeText();
}, [followingProfiles]);
return (
<Layout>
{post ? (
<>
<PostContainer>
.
.
.
<MediumProfileContainer>
<Link to={`/@${post.memberHomeId}`}>
<ProfileIcon size={3.75} profileImg={post.memberImg} />
<div>
<b>{post.memberName}</b>
<span>{post.memberBio}</span>
</div>
</Link>
{!isMyHome && (
<ButtonWrapper>
<FollowButton text={buttonText} handler={handleFollowButton} />
</ButtonWrapper>
)}
</MediumProfileContainer>
</PostContainer>
<TicketsContainer />
<PostBackground />
</>
) : (
<SpinnerWrapper>
<Spinner size={3.5} />
</SpinnerWrapper>
)}
</Layout>
);
};
export default PostTemplate;