노마드코더 인스타그램 클론코딩 바로가기 https://nomadcoders.co/instaclone
피드를 구현하기 전에 프로젝트의 Header를 구현해보자.
위와 같은 Design으로 헤더를 구성하고자 하고 단 몇가지 특이사항을 가진다.
1. 로그아웃 상태에서 즉 로그인 페이지 회원가입 페이지에서는 헤더 X
2. 로그인 상태에서 User의 Avatar 유무에 따른 Profile Icon 제작
const isLoggedIn = useReactiveVar(isLoggedInVar);
이는 Backend의 문제가 아니다. 그냥 headers에 token을 포함해주는 작업을 해주지 않았을 뿐이다.
token을 headers에 포함해줘야 하는 이유는 로그인 상태에서만 작동하는 Query나 Mutation이 있고 이를 위해서는 token을 headers에 담아서 보내는 것으로 인증을 하기 때문이다.
이를 위해서는 apollo client에서 몇가지 설정을 해줘야한다.
1. httpLink : 기본적인 백엔드 uri
2. authLink : setContext를 이용해 기존의 headers에 token을 추가해 모든 request에 token을 headers로 보내준다.
그리고 기존의 client에서는 uri를 설정해줬지만 이제는 httpLink와 authLink를 합친 Link를 설정해준다.
// apollo.js
const httpLink = createHttpLink({
uri:"http://localhost:4000/graphql",
});
const authLink = setContext((_,{headers})=>{
return {
headers:{
...headers,
token:localStorage.getItem(TOKEN),
},
};
});
export const client = new ApolloClient({
cache:new InMemoryCache(),
link:authLink.concat(httpLink),
});
TOKEN을 강제로 변경했을 때 원하는 결과는 로그아웃이 되거나 잘못되었다고 알려줘야 한다.
그러나 TOKEN을 강제로 변경해본 결과 정상적으로 로그인된 상태의 UI가 나타나는 것을 볼 수 있다.
백엔드에서 현재 로그인 한 user의 정보를 가져오는 me query를 구현
me의 typeDefs는 쿼리이고 User객체를 반환한다.
me의 resolvers에서는 protectedResolver를 사용해 구현한다. 왜냐하면 로그인 하지 않은 상태에서 me query에 접근했을때 null을 리턴하기 위함이다.
me query를 frontend에서 사용
query의 데이터는 useQuery를 이용해 가져올 수 있다.
useQuery의 옵션 중 skip이 존재하는데 이는 true인 경우에만 query문을 실행한다.
// useUser.js
// 여기서 hasToken은 isLoggedInVar의 Boolean값이다.
const {data} = useQuery(ME_QUERY,{
skip:!hasToken,
});
가져온 data는 백엔드에서 설계한대로 로그인 한 경우 즉 token이 존재하고 이 token을 가지는 user가 존재하는 경우에는 해당 유저의 data가 return 되고 로그인 되지 않은 경우 즉 token이 존재하지 않거나 token이 존재하지만 이에 매칭되는 user가 없는 경우에는 null 을 return 한다.
useUser Hook 구현
현재 로그인 한 유저의 data를 받는 Query는 한 곳에 종속되지 않고 많은 곳에서 사용 될 수 있기때문에 이와 같은 Query는 Hook으로 구현하는게 좋다.
// useUser.js
function useUser(){
// data가 변경되는 경우마다 실행
useEffect(()=>{
if(data?.me ===null){
logUserOut(history);// LS의 토큰을 삭제하고 home route로 이동(로그인페이지로 이동)
}
},[data]);
return data;
}
export default useUser;
useUser()을 통해서 현재 로그인 한 유저의 query 결과값을 가져오고 로그인 유저에 avatar가 존재하는 경우에는 avatar icon avatar를 지정하지 않았다면 디폴트 icon
const Avatar = ({url=""})=>{
return <SAvatar>{url !== ""? <Img src={url}/> : null}</SAvatar>
}
Avatar 컴포넌트는 사용하는 곳에서 props로 url을 전달 받는데 만약에 전달받지 못한다면 디폴트로 ""를 전달 받는다.
따라서 url이 ""이면 null을 리턴하고 url이 존재한다면 업로드된 이미지를 가져온다.
Header에서 useUser를 이용해 현재 로그인 한 유저의 쿼리문에 따른 결과를 data로 가져오는데
data.me.avatar
와 같이 작성한다면 에러가 발생한다.
그 이유는 data는 Query문을 이용해 서버에 요청한 결과값인데 이를 받는데 시간이 걸린다. 그 말은 바로 data가 undefined인 시점이 존재한다는 것이고 따라서 data.me를 찾을 수 없다고 에러가 발생하는 것이다.
옵셔널 체이닝을 알기 전에는 매번 && 연산자를 통해서 이를 해결해줬었다.
옵셔널 체이닝(?.)은 ?.앞의 평가 대상이 null이나 undefined라면 undefined를 리턴하기 때문에 앞의 평가 대상이 존재하지 않을때 에도 에러가 발생하지 않는 것을 볼 수 있다.
단 ?.를 남용하지 않아야 한다. 무조건 존재하는 곳에는 ?.를 사용하지 않아야 후에 에러를 쉽게 초기에 발견할 수 있다.