1. withCredentials: true?
나의 경우는, 3090이 프론트였고 3095가 백엔드 서버이다. 이전 글에서 포스팅했듯이, 다른 URL로 데이터를 보내는 것에 대한 CORS 문제는 서버 단에서 cors의 origin과 credentials: true 설정을 해줌으로써 해결할 수 있었다. 그렇지만 프론트엔드와 백엔드 서버의 URL이 다를 경우, 백엔드 파트에서 프론트엔드 파트로 쿠키를 생성해줄 수도, 프론트엔드 파트에서 백엔드 파트로 쿠키를 보내줄 수도 없다. 왜냐하면, 쿠키는 기본적으로 같은 origin 에서 HTTP 통신을 하는 경우에 자동으로 들어가는 형태를 띠기 때문이다.
따라서, 요청에 쿠키를 포함하고 싶을 경우 프론트 단에서 axios로 데이터 요청을 보낼 때와 서버에서 CORS 설정을 할 때 각각 withCredentials: true, credentials: true 설정을 해줘야 한다.
// 프론트엔드 파트
const onSubmit = useCallback(
(e) => {
e.preventDefault();
axios
.post(
'http://localhost:3095/api/users/login',
{ email, password },
{
withCredentials: true,
},
)
.then(() => {
revalidate();
})
.catch((error) => {
console.error(error);
});
},
[email, password],
);
if (data) {
return <Redirect to="/workspace/channel" />;
}
// 백엔드 파트
app.use(
cors({
origin: true,
credentials: true,
})
);
2. SWR
SWR은 Next.js를 만든 Vercel에서 만든 fetch를 위한 커스텀 훅 npm 모듈이다.
const { data, error, isValidating, mutate } = useSWR(key, fetcher, options)
SWR의 기본적인 API 형태이다. key에는 API 통신을 할 URL이 들어갈 것이고, fetcher에는 axios 함수가, 그리고 options를 통해서는 이 SWR에 대한 여러가지 설정을 할 수 있다. 아래 예시를 통해 좀 더 자세하게 살펴보자.
// index.tsx
import useSWR from 'swr';
import fetcher from '@utils/fetcher';
const Login = () => {
const { data, error, revalidate } = useSWR('http://localhost:3095/api/users', fetcher, {
dedupingInterval: 100000,
)};
if (error) {
<div>에러 발생</div>
}
};
import axios from 'axios';
const fetcher = (url: string) =>
axios
.get(url, {
withCredentials: true,
})
.then((response) => response.data);
export default fetcher;
위와 같이 import를 해 온다. 그 다음, 실행되는 즉시 fetcher 함수(fether.ts)를 실행시킨다. fetcher 함수가 데이터를 로드했을 때, 응답은 data로 전해지고 에러의 경우 error 인자로 전해진다.
이 useSWR을 사용하게 되면, 주기적으로 호출이 된다.(기본적으로 revalidate() 설정을 해 주면 매번 호출이 되긴 한다.) 이렇게 하는 이유는, 다른 곳에 갔다 다시 돌아올 때마다 자동으로 다시 요청을 보내서 화면을 최신으로 유지해주기 위함이다. 맨 위 onSubmit 함수를 보자. 저 함수에서 axios 요청에 대해 then, 즉 성공했을 경우 revalidate()를 하여 바로 호출이 되도록 하고 있다.
그리고, data가 있을 경우 Redirect하도록 설정함으로써 user 데이터를 확보했는지 여부를 조사한다. 이렇게 된다면 이제, 로그인이 성공한다면 workspace/channel 로 redirect하게 된다.
그런데 이게 필요하지 않을 경우도 있을 것이다. 이 경우에는 useSWR 내부의 dedupingInterval 값으로 조절할 수 있다. 이 명령어 뒤에 원하는 초 만큼 설정한다면, 주기적으로 호출은 되지만 dedupingInterval 기간 안에서는 캐시에서 불러온다.
더 자세한 부분은 SWR GitHub 공식 문서를 참고하면 도움이 될 것이다.
처음부터 하나하나 과정을 살펴보자면,
1. 처음에 data는, 로그인을 하지 않은 상태이므로 false일 것이다.
2. 데이터가 false라면 Redirect 부분이 실행이 되지 않고 그 아래 return 부분부터 실행되며 화면에 login form이 뜰 것이다.
3. 이메일 주소와 비밀번호를 입력한 뒤 로그인 버튼을 누르면 onSubmit이 실행이 되고, axios 요청을 보낸다.
4. 로그인을 성공하면 revalidate()에 따라 다시
const { data, error, revalidate } = useSWR('http://localhost:3095/api/users', fetcher, {
dedupingInterval: 100000,
)};
이 부분이 실행이 된다. 이부분이 다시 시작이 되며, data가 이제는 false가 아니라 내 정보로 채워지게 된다. 내 정보가 들어있으면, 다시 rerendering이 되어서 아래 코드로 흐름이 이동하게 된다.
if (data) {
return <Redirect to="/workspace/channel" />;
}
5. 리액트 라우터의 페이지를 workspace/channel 로 옮긴다.
// Channel.tsx
import React from 'react';
import Workspace from '@layouts/Workspace';
const Channel = () => {
return (
<Workspace>
<div>로그인하신 것을 축하드려요!</div>
</Workspace>
);
};
export default Channel;
6. 로그아웃 버튼을 누르는 순간, 아래 코드에서 revalidate()에 의해 data에 원래는 정보가 들어있다가, false가 된다. (api/users/logout API에 의함)
import React, { FC, useCallback } from 'react';
import fetcher from '@utils/fetcher';
import useSWR from 'swr';
import axios from 'axios';
import { Redirect } from 'react-router';
const Workspace: FC = ({ children }) => {
const { data, error, revalidate } = useSWR('http://localhost:3095/api/users', fetcher);
const onLogout = useCallback(() => {
axios
.post('http://localhost:3095/api/users/logout', null, {
withCredentials: true,
})
.then(() => {
revalidate();
});
}, []);
if (!data) {
return <Redirect to="/login" />;
}
return (
<div>
<button onClick={onLogout}>로그아웃</button>
{children}
{/* Channel/index.tsx에서 <Workspace> 안에 들어있는 요소들을 children으로 칭한다. */}
</div>
);
};
export default Workspace;
그래서, 로그아웃 버튼을 누르는 즉시 다시 로그인 페이지로 이동한다.
SWR이 전역적으로 데이터를 관리하고 있는 상태인 것이다!
여기서 거슬리는 점
로그인 한 뒤에는 원래 어느 서비스가 그렇듯이 로그인 페이지와 회원가입 페이지로 이동할 수 없게 되어있다. 그리고 위 코드도 그대로 구현되어있다. 문제는, 움짤에서 보면 로그인한 뒤 login으로 주소 입력을 했을 때 잠깐 로그인 페이지가 등장했다가 다시 원래 페이지로 돌아간다는 것이다. 참 보기 싫은 부분이 아닐 수 없다. 이 문제는,
if (data === undefined) {
return <div>로딩중...</div>;
}
if (data) {
return <Redirect to="/workspace/channel" />;
}
이렇게, 데이터가 없을 때 리턴할 페이지를 미리 지정해 줌으로써 해결할 수 있다.
훨씬 보기 좋아졌다!
'Web' 카테고리의 다른 글
[React + TypeScript] API통신을 이용한 워크스페이스 만들기 (0) | 2021.07.09 |
---|---|
[API통신] SWR - revalidate와 mutate의 차이점 (+optimistic, pessimistic UI) (0) | 2021.07.06 |
[TypeScript] 회원가입 페이지 구현하기 (0) | 2021.07.05 |
[TypeScript] 점진적 타이핑 1 (0) | 2021.06.30 |
[TypeScript] 자바스크립트에서 타입스크립트로 변환하기 (0) | 2021.06.30 |