정리할 내용

  • [] profile route Container, Presenter, Queries, index로 나누기
  • [] route의 : 뭔지 찾아보기
  • [] graphql에서 count하는 방법정리 ( UserConnection , aggregate , count

게시물의 Username 누르면 프로필 보여주기

링크로 /username으로 보내줍니다.

//ProfilePresenter.js

 <UserColumn>
            <Link to={`/${username}`}>
              <FatText text={username} />
            </Link>
            <Location>{location}</Location>
          </UserColumn>

유저 검색의 아이디를 누르면 프로필 보여주기

//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>
);

그렇다면 /username이 어떻게 프로필을 보여주는지 정리해보겠습니다. 우선 프로필을 보여준다는 것은 특정 router를 보여준다는 것이겠죠? 그 route는 profile일 것입니다.

아래는 Route.js의 일부인데, logedInRouter는 로그인 상태일 때 보여주는 Router입니다. Switch는 여러 Route중에서 하나를 고르는 것입니다. 그 중에 먼저 걸리는 것으로 보내주죠. :의 의미는 정리해봐야 할 것 같습니다.

//Route.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>
);

(...중략...)

유저를 누르게 되면 프로필 컴포넌트로 향하게 됩니다.
여기서도 withRouter를 사용해서 match의 param정보를 얻어와서 Query요청을 보냈습니다.

대부분의 루틴이 url로 요청을 하면 해당 url로 보내고 그 route는 router객체가 보낸 match history location을 사용해서 서버에 데이터를 요청하는 방식을 사용합니다.

// 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} />;
});

ProfileQueries.js

Query에서는 logout을 하는 부분이 평소와 다릅니다. logout은 client에서 만든 resolver입니다. client것을 사용하기 위해서는 @clinet를 붙혀줍니다.

//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
  }
`;

ProfilePresenter.js

Presenter에서 많이 헛수고를 했다…

이유는 Container에서 서버에 요청한 데이터가 없는데 사용하려고 해서이다. 정확한 이유는 더 알아봐야겠지만 컴포넌트가 3번정도 render를 하려고하는데 첫번째와 두번째에는 데이터가 존재하지 않고 세번째 때 데이터가 응답되는데 그 데이터를 사용하려고 해서 에러가 발생하는 것 같습니다.

  • 해결방법
    • if문으로 loading인지 부터 체크함.
    • data.seeUser이 존재할 때 만 데이터를 사용하는 방식 적용
import React from "react";
import styled from "styled-components";
import Avatar from "../../Components/Avatar";
import Loader from "../../Components/Loader";
import FatText from "../../Components/FatText";
import PostCard from "../../Components/PostCard";
import Button from "../../Components/Button";
const Wrapper = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
`;

const Header = styled.div`
  display: flex;
  justify-content: flex-start;
  margin-bottom: 50px;
`;

const HeaderColumn = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: space-around;

  &:first-child {
    margin: 0 100px;
  }
`;

const HeaderList = styled.ul`
  display: flex;
`;

const HeaderItem = styled.li`
  font-size: 16px;
  margin-right: 20px;
`;

const PostContainer = styled.div`
  display: grid;
  grid-template-columns: repeat(3, 1fr);
`;
const MetaItem = styled.li`
  opacity: 0.5;
  cursor: pointer;
  transition: opacity 0.2s linear;
  margin-right: 50px;
  padding: 15px 10px;
  border-top: 2px solid rgb(0, 0, 0, 0);
`;
const MetaList = styled.ul`
  border-top: ${props => props.theme.boxBorder};
  margin-bottom: 50px;
  display: flex;
  justify-content: center;
  ${MetaItem}:hover {
    border-top: 2px solid rgb(0, 0, 0, 1.2);
    opacity: 1;
  }
`;

export default ({ loading, data, logOut }) => {
  if (loading) {
    return (
      <Wrapper>
        <Loader />
      </Wrapper>
    );
  }
  if (!loading && data && data.seeUser) { // 이렇게 강제로 데이터가 있을 경우에만 데이터 적용 
    console.log(data.seeUser.posts);
    const { seeUser: user } = data;
    return (
      <Wrapper>
        <Header>
          <HeaderColumn>
            <Avatar size="lg" url={user.avatar} />
          </HeaderColumn>
          <HeaderColumn>
            <HeaderList>
              <HeaderItem>
                <FatText size="28" text={user.username} />
              </HeaderItem>
              {user.isSelf && (
                <HeaderItem>
                  <Button onClick={logOut} text="로그아웃" />
                </HeaderItem>
              )}
            </HeaderList>
            <HeaderList>
              <HeaderItem>
                게시물 <FatText text={Number(user.postCount)} />
              </HeaderItem>
              <HeaderItem>
                팔로워 <FatText text={Number(user.followerCount)} />
              </HeaderItem>
              <HeaderItem>
                팔로잉 <FatText text={Number(user.followingCount)} />
              </HeaderItem>
            </HeaderList>
            <HeaderList>
              <HeaderItem>
                <FatText text={user.fullName} />
              </HeaderItem>
            </HeaderList>
          </HeaderColumn>
        </Header>
        <MetaList>
          <MetaItem>게시물</MetaItem>
          <MetaItem>저장됨</MetaItem>
          <MetaItem>태그됨</MetaItem>
        </MetaList>
        <PostContainer>
          {user.posts.map(post => (
            <PostCard
              key={post.id}
              url={post.files[0].url}
              likeCount={post.likeCount}
              commentCount={post.commentCount}
            />
          ))}
        </PostContainer>
      </Wrapper>
    );
  }
};