처음에는 회원 가입한 유저와 로그인한 유저의 상태를 따로 관리해줘야한다고 생각했다. 아직 useState로 상태관리를 한다는 개념이 데이터를 저장하고 불러와 사용한다는 개념이랑 헷갈려서 그렇게 생각했던 것 같다.
하지만 작성하다보니 구지 이 둘의 상태를 나눠서 관리할 필요가 없으며 마찬가지로 회원가입 시에 만들어진 유저의 상태를 관리할 필요도 없다고 깨달았고, const [users, setUsers] = useState(null);
로만 유저의 상태관리를 해주는 것으로 변경했다.
다만 여기서 문제점은 로그인 여부가 전역적으로 상태관리가 필요한데 현재 내가 작성한 컴포넌트는 최상단 컴포넌트도 아니었을 뿐더러 부모-자식 관계로 해당 상태를 props로 내려줄 수도 없는 상황이라 contextAPI나 redux로 user의 상태 관리를 리팩토링 해줘야한다는 필요성을 느꼈다.
또한, 로그인 여부를 확인하고 이 상태를 가지고 다른 로직들을 작성해줘야하기 때문에 로그인 시 유저의 액세스토큰을 저장해주는 작업이 필요했다.
const [formData, setFormData] = useState({ id: '', password: '', nickname: '', isLogin: false });
const [users, setUsers] = useState(null);
const [loginUsers, setLoginUsers] = useState(null);
// 회원가입 api 로직
const handleRegister = async (user) => {
const { data } = await api.post('/register', user);
setUsers([...users, data]);
};
// 회원가입 폼 제출
const onSubmitRegister = (event) => {
event.preventDefault();
// 유효성 검사
// 코드 생략
handleRegister({ ...formData, isLogin: true });
navigate('/home');
setFormData({ id: '', password: '', nickname: '' });
};
// 로그인 api 로직
const handleLogin = async (user) => {
try {
const { data } = await api.post('/login', {
id: user.id,
password: user.password
});
console.log(user);
if (data.success) {
toast.success('로그인 성공!');
setLoginUsers({ id: userId, nickname: nickname });
navigate('/home');
} else {
toast.error('로그인에 실패했습니다.');
}
} catch (error) {
console.log('Error logging in', error);
toast.error('로그인 실패: 서버 오류입니다.');
}
};
// 로그인 폼 제출
const onSubmitLogin = (event) => {
event.preventDefault();
if (!formData.id || !formData.password) {
toast.warn('이메일과 비밀번호를 입력해주세요.');
return;
}
handleLogin(formData);
setFormData({ id: '', password: '', nickname: '' });
};
로그인과 회원가입까지는 강의 내용을 보며 어느정도 로직을 작성해볼 수 있었는데, 유저의 로그인 상태를 구독하는 비동기 함수를 작성할 때는 headers에 추가로 작성해주는 부분이 있었고 이 부분에 대한 개념이 잘 이해가 되지 않아 해설 강의를 보며 코드를 작성했다. axios의 매서드를 사용할 때 경우에 따라 인자에 필요한 정보를 넣어준다는 것을 알게 됐다.
axios.get(url[, config])
여기서 config 부분이 { headers: { Authorization:
Bearer ${accessToken}
}
- 추가로 알게 된 내용
[,config] : 선택적 매개변수 표기법 의미
-> 대괄호 : 선택,
-> config(객체) : 써도 되고, 안써도 된다,
-> ‘,’앞에 콤마가 있는 이유 : 2번째 인자
redux Toolkit
import { createSlice } from '@reduxjs/toolkit';
const userSlice = createSlice({
name: 'users',
initialState: {
user: null
},
reducers: {
setUser: (state, action) => {
state.user = action.payload;
}
}
});
export const { setUser } = userSlice.actions;
export default userSlice.reducer;
// 회원가입 api 로직
export const register = async ({ id, password, nickname }) => {
try {
const response = await authApi.post('/register', {
id: id,
password: password,
nickname: nickname
});
// console.log('회원가입 데이터 확인', response.data);
return response.data;
} catch (error) {
console.log(error?.response?.data?.message);
toast.warn(error?.response?.data?.message);
}
};
// 회원가입
const onSubmitRegister = async (event) => {
event.preventDefault();
// 유효성 검사 코드 생략
const response = await register({ ...formData });
if (response) {
setFormData({ id: '', password: '', nickname: '' });
toast.success('회원가입이 완료되었습니다.');
}
};
// 로그인 api 로직
export const login = async ({ id, password }) => {
try {
const response = await authApi.post('/login?expiresIn=30m', {
id: id,
password: password
});
console.log('로그인 데이터 확인', response.data);
localStorage.setItem('accessToken', response.data.accessToken);
return response.data;
} catch (error) {
console.log(error?.response?.data?.message);
toast.warn(error?.response?.data?.message);
}
};
// 로그인
const onSubmitLogin = async () => {
const { userId, nickname, avatar } = await login({
id: formData.id,
password: formData.password
});
toast.success('로그인 성공!');
dispatch(setUser({ userId, nickname, avatar }));
navigate('/home');
};
export const getUserInfo = async () => {
const accessToken = localStorage.getItem('accessToken');
if (accessToken) {
try {
const response = await authApi.get('/user', {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`
}
});
return response.data;
} catch (error) {
localStorage.clear();
toast.warn('accessToken이 만료되었습니다.');
}
}
};
참고 자료