이번 주말동안 크게 한 것은 로그인 상태관리(서버)를 Redux에서 ReactQuery로 바꿨다는 점
작업하는동안 크게 느꼈던 것 부터 적고가자..
- Redux는 클라이언트의 상태관리, React Query는 서버의 상태관리
- HTTP 통신 시 요청(requset)과 응답(response)의 데이터를 꼭 확인하면서 작업하자.
- useQuery의 Promise함수는 꼭 response를 return을 해야 data에 값이 들어온다.
const { data, isLoading, error } = useQuery(
['isLoggedIn'],
async () =>
await axios
.get('/api/users')
.then((res) => {
✅ if (res.data.isAuth === false) return false;
✅ if (res.data._id) return true;
})
.catch((error) => {
console.log(error.response);
}),
);
- 데이터의 여부와 boolean의 상태를 잘 파악하자.
const data1 = false;
console.log(data1); // false
console.log(!data1); // true
const data2 = 'something';
console.log(data2); // 'something' 즉, if 조건에 사용하면 true
console.log(!data2); // false;
잘가 Redux, 안녕 React Query
불과 금요일까지만 해도 유저의 접속 상태를 리덕스로 관리하고 리액트쿼리는 그저 쿼리 키만 만드는 용도로 사용했었다.
하지만 틀렸다는 걸 알고 유저의 접속상태를 리액트쿼리로만 관리하기 위해 처음부터 다시 시작했다.
로그인, 로그아웃, 회원가입, 인증, 유저정보가져오는 API부터 만들고 POSTMAN으로 확인해가면서 작업했다.
const express = require('express');
const { auth } = require('../middleware/auth');
const { User } = require('../model/user');
const router = express.Router();
/**
* 파라미터 변수 뜻
* req : request 요청
* res : response 응답
*/
/**
* @path {GET} http://localhost:8000/api/users
* @description 유저정보 가져오기
* @returns {user || false}
*/
router.get('/users', auth, (req, res, next) => {
User.findOne({ _id: req.user._id }, (err, result) => {
if (err) return res.json({ isLoggedIn: false, err });
return res.json(req.user || false);
});
});
/**
* @path {POST} http://localhost:8000/api/users
* @description 회원가입
* @returns {registerSuccess, user}}
*/
router.post('/users', async (req, res) => {
// client의 req.body에서 필요한 정보들을 가져와 DB에 넣어준다.
try {
const exNickname = await User.findOne({
nickname: req.body.nickname,
});
const exEmail = await User.findOne({
email: req.body.email,
});
if (exNickname) return res.status(403).send('이미 사용 중인 닉네임입니다.');
if (exEmail) return res.status(403).send('이미 사용 중인 이메일입니다.');
const user = await User.create({
// DB에 유저 정보 생성
nickname: req.body.nickname,
email: req.body.email,
password: req.body.password,
});
res.status(200).json({ registerSuccess: true, user: res.user });
} catch (error) {
console.error(error);
}
});
/**
* @path {POST} http://localhost:8000/api/users/login
* @description 로그인
* @returns {loginSuccess, userId}
*/
router.post('/users/login', (req, res) => {
// 요청된 이메일을 DB에서 찾는다
User.findOne({ email: req.body.email }, (err, user) => {
if (err) {
console.log(err);
return res.status(400).send(err);
}
if (!user) {
return res.status(403).json({ loginSuccess: false, message: '이메일 틀림' });
}
// DB에 이메일이 있다면 DB에서 비밀번호가 맞는지 확인(comparePassword 메소드 생성해서 확인)
user.comparePassword(req.body.password, (err, isMatch) => {
if (!isMatch) {
return res.status(403).json({ loginSuccess: false, message: '비밀번호 틀림' });
}
// 비밀번호까지 맞다면 토큰을 생성(generateToken 메소드 생성해서 확인)
user.generateToken((err, user) => {
if (err) return res.status(400).send(err);
// 토큰을 쿠키에 저장
return res.cookie('x_auth', user.token).status(200).json({ loginSuccess: true, message: '로그인 성공', userId: user._id });
});
});
});
});
/**
* @path {POST} http://localhost:8000/api/users/login
* @description 로그아웃
* @returns {logoutSuccess}
*/
router.get('/users/logout', auth, (req, res) => {
// req.user는 auth에서 받아온 request
User.findOneAndUpdate({ _id: req.user._id }, { token: '' }, (err, user) => {
if (err) return res.json({ success: false, err, message: '로그아웃 실패' });
return res.status(200).send({
logoutSuccess: true,
message: '로그아웃 성공',
});
});
});
/**
* @path {GET} http://localhost:8000/api/users/auth
* @description 인증 미들웨어
*/
router.get('/users/auth', auth, (req, res) => {
// 여기까지 미들웨어(auth.js)를 통과해 왔다는 얘기는 Authentication이 True라는 말
// 클라이언트에게 유저 정보 전달
res.status(200).json({
nickname: res.user.nickname,
email: res.user.email,
role: res.user.role,
_id: res.user._id,
isAdmin: res.user.role === 0 ? false : true, // role이 0이면 일반 유저, 그외는 관리자
isAuth: true,
});
});
module.exports = router;
모든 API가 잘 동작하는걸 확인하고 클라이언트(프론트)를 만들어서 페이지와 UI를 만들어서 코드를 작성해갔다.
물론 axios통신으로 요청하고 응답받는 것 까지는 쉬웠는데 그 이후에 로그인 된 정보와 상태를 관리하고싶었다.
처음엔 useState와 useEffect로 로그인 상태를 확인해서 페이지를 리렌더링 하는 방법을 택했지만 다른 페이지로 넘어가면 state가 사라지기 때문에 효과가 없었다.
ReactQuery
그래서 리액트쿼리를 적용하는 법을 찾아봤고 가장 중요하다고 뼈저리게 느꼈던건 쿼리 키,Promise 함수 였다.
// 기본적인 구조
const { data, isLoading, error } = useQuery(['쿼리 키'], () => {
axios.get('url').then((response) => response);
},
{ options },
);
@tanstack/react-query/useQuery
쿼리 키는 배열의 형태로 넣어야했고, 프로미스의 response에서는 반드시 return값이 있어야했다. ( 한 3~4시간 헤맸다.)
그래야 data에 resonse의 데이터들이 들어간다.
그렇게 리액트쿼리로 계속 요청을 보내면서 유저의 로그인 상태를 가져올 수 있었고 그걸 이용해서 useEffect로 로그인 상태를 state로 변경해서 원하는대로 렌더링할 수 있었다.
const [isLoggedIn, setIsLoggedIn] = useState(false);
const { data, isLoading, error } = useQuery(
['isLoggedIn'],
async () =>
await axios
.get('/api/users')
.then((res) => {
if (res.data.isAuth === false) return false;
if (res.data._id) return true;
})
.catch((error) => {
console.log(error.response);
}),
);
useEffect(() => {
if (data) {
console.log('로그인 되어있다', data);
setIsLoggedIn(true);
} else {
console.log('로그인 되어있지않다', data);
setIsLoggedIn(false);
}
}, [isLoggedIn, data]);
if (error) return error;
return isLoading ? (
<div>Loading...</div>
) : (
<>
<div>메인...</div>
</>
);
쿼리의 데이터가 로그인 여부에 따라 true, false로 바뀌게 해놨다. 이걸 하려고 대체 몇일을 고생했는지...
몇일동안 고생하면서 느낀 것들을 적용하면서 이제 실제 서비스를 위한 UI들도 만들고 스타일도 적용해야겠다.
웹팩 개발환경 & 배포환경
스타일을 적용하기 위해서 emotion을 설치하던 중... 웹팩에서 모듈을 불러오지 못하는 상황을 마주했다.
일단 결론은 다음과 같이 설치해서 해결했다는 점.
"@emotion/core": "^11.0.0",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@emotion/babel-plugin": "^11.10.5",
이런 저런 웹팩설정 찾아보다가 실제 프로젝트 진행할 때는 개발용과 배포용을 구분해서 설정한다는 것을 알았다.
그래서 미리 준비해보고자 개발용과 배포용을 셋팅을 나눠봤다.
기본적인 입출력과 모듈 설정은 동일하고 개발용서버,플러그인이 조금 다른 정도였다.
이 글 참고하면서 나눠봤다.