문제 : 로그인시 토큰 전달하여 유저정보를 GET하려는데 토큰 전달이 제대로 안됨
(비동기 통신에서 처리 순서의 문제)
로직 : 로그인 시 3가지를 실행
문제의 코드
// ✅ Login.jsx
// 로그인버튼 클릭 핸들러
const onIsLoggedInHandler = async () => {
if (isValidId && isValidPw) {
login(userId, userPw);
setIsLoggedIn((prevIsLoggedIn) => !prevIsLoggedIn);
} else {
alert("입력하신 값을 확인해주세요.");
}
// 로그인 유저정보 리듀서에 전달하기
const accessToken = localStorage.getItem("loggedInUserToken");
const loggedInUserInfo = await getLoggedInUserInfo(accessToken);
dispatch(addUser({ loggedInUserInfo, accessToken }));
};
// ✅ users.js
// 로그인
export const login = async (userId, userPw) => {
const response = await axios.post(
"https://moneyfulpublicpolicy.co.kr/login",
{ id: userId, password: userPw }
);
// 로그인시 로컬스토리지에 토큰 저장하기
const loggedInUserInfo = response.data;
const loggedInUserToken = loggedInUserInfo.accessToken;
localStorage.setItem("loggedInUserToken", loggedInUserToken);
};
// 로그인된 유저정보 가져오기
export const getLoggedInUserInfo = async (token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.get(
"https://moneyfulpublicpolicy.co.kr/user",
config
);
return response.data;
};
시도
해결
결국 비동기 처리순서의 문제가 맞았음. login() 앞에 await붙이는게 중요한 해결 포인트였음
// ✅ Login.jsx
// 로그인버튼 클릭 핸들러
const onIsLoggedInHandler = async () => {
if (isValidId && isValidPw) {
await login(userId, userPw);
setIsLoggedIn((prevIsLoggedIn) => !prevIsLoggedIn);
// 로그인 유저정보 리듀서에 전달하기
const accessToken = localStorage.getItem("loggedInUserToken");
const loggedInUserInfo = await getLoggedInUserInfo(accessToken);
dispatch(addUser({ loggedInUserInfo, accessToken }));
} else {
alert("입력하신 값을 확인해주세요.");
}
};
문제 : 새로고침시 계속 letter가 중복으로 덧붙는 문제
리듀서로 받아오는 letters가 계속 배열 속의 배열 혹은 8개 16개 이런식으로 정확한 db 객체배열을 반환하지 못함
시도 : data, letters를 계속 콘솔에 찍어보며 await의 위치, 의존성 배열 값 수정, dispatch로 보내는 인자를 [data], [...data] 등 계속 바꿔보기, 리듀서의 return값을 [newLetter, ...state], [...newLetter, ...state] 등 계속 수정을 거듭함
원인 : 리듀서의 반환값에서 state와 newletter의 순서에서 생긴 오류!
해결
// ✅ 컴포넌트
useEffect(() => {
const letters = async () => {
const data = await getLetters();
dispatch(addLetter(data));
};
letters();
}, [dispatch]);
const filteredLetters = letters.filter(
(letter) => letter.writedTo === activeMember
);
// ✅ 리듀서
const initialState = [];
const lettersSlice = createSlice({
name: "letters",
initialState: initialState,
reducers: {
addLetter: (state, action) => {
const newLetter = action.payload;
return [...state, ...newLetter];
},
deleteLetter: (state, action) => {
const letterId = action.payload;
return state.filter((letter) => letter.id !== letterId);
},
editLetter: (state, action) => {
const { id, editingText } = action.payload;
return state.map((letter) => {
if (letter.id === id) {
return { ...letter, content: editingText };
}
return letter;
});
},
},
});
렌더링할때 내가 정말 원했던건 계속 붙여주는 게 아니라, 페이지 렌더링이 됐을때 가장 최신상태의 letter들을 get하는거였다
useEffect의 의존성배열을 잘못 설정한줄알고 그 부분만 계속 수정하면서 슈팅했는데, 그게 아니라 addLetter가 아닌 letter를 초기화하는 action creator를 새로 만들어줘야하는 거였다!!!
payload로 get한 최신 letter들을 넘겨주고, state를 action.payload로 만들어주면 최신화하는 action creator 완성!!
근데 그 Outlet이 들어있는 컴포넌트 안에서
useEffect로 토큰이 있으면 보여주고, 없으면 로그인페이지로
이동하게 만들면 로그인상태 유지하는것처럼 보여질 수 있음
시도2. 로그아웃하면 navigate로 login페이지로 넘어가게 했는데 말 안들음
(빈화면으로 나옴)
시도3. authlayout에서 useEffect로 token체크하고 없으면 login으로 navigate 줘도 안됨
(새로고침하면 로그인화면 잘 나옴)
발생하고 있는 문제
로그인 클릭시 서버 로그인, 유저정보 가져오기, 토큰 로컬스토리지에 붙이기 는 다 되는데, 화면이 로그인화면으로 유지됨
-> 새로고침하면 홈화면 나옴
로그아웃하면 로컬스토리지 토큰은 없어지는데 화면은 로그인화면으로 전환되지 않음
원하는것 : 새로고침할때마다, 로그인버튼 클릭시, 로그아웃버튼 클릭시 token유무를 파악해서 있으면 authlayout 보이게, 없으면 login화면으로 나오게 만들고싶음
(navigate 이용하지 않고 알아서 token 유무 파악하게 할수 없나?)
혼자 생각해본것
👍🏻 해결
-> 화면이 리렌더링되면 리듀서의 state값은 초기값으로 변함
(리듀서의 state는 서버에 저장한게 아니기때문에 새로고침하면 날아가는것)
-> 새로고침하면 날아가니까 라우터에서 useEffect로 로그인때 했던것처럼 그대로 유저정보랑 토큰을 state에 넣어주는 로직 넣기
-> 새로고침해도 state에 토큰이 남아있기 때문에(전 단계 로직으로 인해) 토큰으로 판별시 로그인상태로 인식하여 홈화면을 유지할 수 있음
-> 로그아웃 클릭시, 리듀서의 state를 초기값으로 초기화(초기값으로 state를 바꿔주는 action creator 추가)
(토큰값 없어지므로 화면이 로그인화면으로 전환됨)
-> 이렇게하면 굳이 로그인, 로그아웃 버튼 클릭시 navigate로 페이지 이동하게끔 하드코딩하지 않아도 state 변경에 따라 알아서 화면을 전환한다!!
(리액트에서 리렌더링 조건 3가지 : state의 변화, props 변화, 부모 컴포넌트 변화 중 state변화에 해당)
Detail 상세페이지에서 해당 letter 삭제하기 중 오류
// ✅ Detail.jsx - 선택한 letter의 상세페이지
export default function Detail() {
const dispatch = useDispatch();
const letters = useSelector((state) => state.letters);
const [foundLetter, setFoundLetter] = useState({});
const navigate = useNavigate();
const { id } = useParams();
useEffect(() => {
// ⭐️ 1)
const selectedLetter = letters.find((letter) => letter.id === id);
// ⭐️ 2)
setFoundLetter(selectedLetter);
}, [id, letters]);
// ⭐️ 3)
if (!foundLetter) return null;
// ⭐️ 4)
const { avatar, nickname, createdAt, writedTo, content } = foundLetter;
참고
중요한점