Chapter9. Profile
이번 시간에는 노마드 코더의 Profile 만들기 내용을 정리하면서 생겨난 에러와 함께 여러 내용을 정리하도록 하겠습니다.
정리할 내용은 아래와 같습니다.
- Profile Folder / Container, Presenter, Queris, Index
- Error
userLink
Post에서 유저 이름(username)을 누르면 프로필의 탭으로 전환하는 부분입니다.
src/Routes/Profile/ProfilePresenter.js
<UserColumn>
<Link to={`/${username}`}>
<FatText text={username} />
</Link>
<Location>{location}</Location>
</UserColumn>
Link to로 username을 연결하여 React-Helmet으로 구성되어 있는 프로파일로 전환하도록 username의 fattext를 감싸줍니다.
Profile
src/Component/UserCard.js
export default ({ id, username, avatar, isFollowing, isSelf }) => (
<Wrapper>
<Avatar size={"md"} url={avatar} />
<Link to={`/${username}`}>
<FatText text={`${username}`} />
</Link>
{!isSelf &&
(isFollowing ? (
<FollowButton isFollowing={isFollowing} id={id} />
) : (
<FollowButton isFollowing={isFollowing} id={id} />
))}
</Wrapper>
);
어떻게 <Link to={
/${username}}>
이 부분이 동작을 하느냐는 위에서 잠깐 설명 해드렸습니다. 하지만 정확하게 어디서 일어나는지 말슴드리겠습니다.
우선 src/Component/Routes.js
를 보도록 합니다.
const LogedInRouter = () => (
<Switch>
<Route exact path="/" component={Feed} />
<Route path="/explore" component={Explore} />
<Route path="/notifications" component={Notifications} />
<Route path="/search" component={Search} />
<Route path="/:username" component={Profile} />
</Switch>
);
여기서 switch는 해당 라우터를 하나만 전송시켜주는 것이라고 앞서 설명 드렸습니다. username은 Profile 컴포넌트로 연결이 되어 있습니다. 여기서 withRouter로 감싸 default 하게 되고 withRouter는 match의 params 정보를 가지고 있습니다. 이러한 정보로 Query 요청을 보냅니다.
url 요청이 들어오면 해당 url로 보내고 route는 router 객체가 보낸 match history location을 사용하여 서버에 데이터를 요청하게 됩니다. 이러한 내용을 이해하신 뒤 아래와 같은 코드를 보면 이해가 더 편합니다.
src/Routes/Profile/ProfileContainer.js
import React from "react";
import ProfilePresenter from "./ProfilePresenter";
import { useQuery, useMutation } from "react-apollo-hooks";
import { withRouter } from "react-router-dom";
import { SEE_USER, LOCAL_LOG_Out } from "./ProfileQueries";
export default withRouter(({ match: { params: { username } } }) => {
const { data, loading } = useQuery(SEE_USER, {
variables: {
username
}
});
const logOut = useMutation(LOCAL_LOG_Out);
return <ProfilePresenter logOut={logOut} data={data} loading={loading} />;
});
여기서 Nomard는 import { withRouter } from "react-router-dom";
이 부분을 import withRouter from "react-router-dom/withRouter";
이렇게 작성을 하였는 withRouter는 react-router-dom으로 expose 되었으니 참고 하시어 코드를 작성하시면 좋을 듯 합니다.
ProfileQueries.js
Query에서 logOut을 하기 위해서는 client에서만 사용하는 resolver를 만들어야합니다. 또한 프론트엔드 첫번째 강의에서 나오듯이 @client를 붙여주어 사용합니다.
src/Routes/Profile/ProfileQueries.js
import { gql } from "apollo-boost";
export const SEE_USER = gql`
query seeUser($username: String!) {
seeUser(username: $username) {
id
avatar
username
fullName
posts {
id
likeCount
commentCount
files {
id
url
}
}
postCount
followingCount
followerCount
isFollowing
isSelf
}
}
`;
export const LOCAL_LOG_Out = gql`
mutation {
logUserOut @client
}
`;
이렇게 Log_Out을 보내면 아폴로의 LocalState에서
src/Apollo/LocalState.js
logUserOut: (_, __, { cache }) => {
localStorage.removeItem("token");
window.location = "/";
return null;
}
이 기능이 작동하여 캐쉬를 지우고 token을 삭제하여 윈도우를 “/” 초기 상태로 돌려주며 로그아웃 하게 됩니다.
ProfilePresenter.js
src/Routes/Profile/ProfilePresenter.js
import React from "react";
import styled from "styled-components";
import { Helmet } from "react-helmet";
import Loader from "../../Components/Loader";
import Avatar from "../../Components/Avatar";
import FatText from "../../Components/FatText";
import FollowButton from "../../Components/FollowButton";
import SquarePost from "../../Components/SquarePost";
import Button from "../../Components/Button";
const Wrapper = styled.div`
min-height: 100vh;
`;
const Header = styled.header`
display: flex;
align-items: center;
justify-content: space-around;
width: 80%;
margin: 0 auto;
margin-bottom: 40px;
`;
const HeaderColumn = styled.div``;
const UsernameRow = styled.div`
display: flex;
align-items: center;
`;
const Username = styled.span`
font-size: 26px;
display: block;
`;
const Counts = styled.ul`
display: flex;
margin: 15px 0px;
`;
const Count = styled.li`
font-size: 16px;
&:not(:last-child) {
margin-right: 10px;
}
`;
const FullName = styled(FatText)`
font-size: 16px;
`;
const Bio = styled.p`
margin: 10px 0px;
`;
const Posts = styled.div`
display: grid;
grid-template-columns: repeat(4, 200px);
grid-template-rows: 200px;
grid-auto-rows: 200px;
`;
export default ({ loading, data, logOut }) => {
if (loading === true) {
return (
<Wrapper>
<Loader />
</Wrapper>
);
} else if (!loading && data && data.seeUser) {
const {
seeUser: {
id,
avatar,
username,
fullName,
isFollowing,
isSelf,
bio,
followingCount,
followersCount,
postsCount,
posts
}
} = data;
return (
<Wrapper>
<Helmet>
<title>{username} | Prismagram</title>
</Helmet>
<Header>
<HeaderColumn>
<Avatar size="lg" url={avatar} />
</HeaderColumn>
<HeaderColumn>
<UsernameRow>
<Username>{username}</Username>{" "}
{isSelf ? (
<Button onClick={logOut} text="Log Out" />
) : (
<FollowButton isFollowing={isFollowing} id={id} />
)}
</UsernameRow>
<Counts>
<Count>
<FatText text={String(postsCount)} /> posts
</Count>
<Count>
<FatText text={String(followersCount)} /> followers
</Count>
<Count>
<FatText text={String(followingCount)} /> following
</Count>
</Counts>
<FullName text={fullName} />
<Bio>{bio}</Bio>
</HeaderColumn>
</Header>
<Posts>
{posts &&
posts.map(post => (
<SquarePost
key={post.id}
likeCount={post.likeCount}
commentCount={post.commentCount}
file={post.files[0]}
/>
))}
</Posts>
</Wrapper>
);
}
return null;
};
이상으로 Chaper 9.Profile 편을 정리하였고 다음편에는 Nomard가 숙제로 내어준

Explorer와 Photo를 눌렀을 때 큰 화면으로 전환되는 Photo화면과 함께 Post 기능을 참고해서 만들것 좋아요 댓글 기능 넣기를 하도록 하겠습니다.
Leave a comment