axios
를 통해 서버와 통신하여 로그인을 진행하는 로그인 컴포넌트 구현Redux
를 이용해 유저 정보를 state로 관리App
Redux
요구사항
- id와 pw를 state, input으로 관리해야 합니다.
- 로그인 버튼 클릭 시 id나 pw 둘 중 하나라도 값이 비어있다면, alert창을 통해 submit event를 종료시킵니다.
- axios를 통신하는 동안 button을 다시 클릭할 수 없습니다.
- axios 통신 결과에 따라 user state (redux)를 변경하거나,
Error Msg를 지정합니다.- Error Msg는 있을 때에만 출력되며, 1.5초 뒤 사라집니다.
Error Msg가 사라지며 button을 다시 클릭할 수 있습니다.
API 명세서
- 반드시 body(obj)를 같이 보내줘야 함.
- body.id : 유저 id
- body.password : 유저 pw
- 로그인 실패 시
- code : 400 = body 값이 비어 있을 때
- code : 401 = 존재하지 않는 id일 때
- code : 402 = 비밀번호가 틀렸을 때
- 로그인 성공 시
- code : 200
- userInfo = object, user의 data가 넘어 옴
요구사항
- 로그인한 유저에 따라서 다른 이름을 출력해야 합니다.
- 로그아웃 버튼을 클릭하면 user state (redux)를 초기값으로
되돌립니다. = 로그아웃 동작을 수행합니다.
로그인 성공 및 로그아웃 시
로그인 실패 시
ID와 Password의 <input>
에 값이 입력되었을 때, onChange
와 useState
를 통해 입력된 값을 관리했다.
// LoginComponent.jsx
const [id, setId] = useState("");
const [password, setPassword] = useState("");
<input type="text" id="id" value={id} onChange={(e) => setId(e.target.value)} />
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)}/>
LoginFunc
)먼저 로그인 버튼이 눌렸을 때, input이 채워지지 않은 경우와 input이 채워진 경우 크게 두 가지로 나누어서 처리했다.
ID와 Password에 값이 입력되었는지 체크하고, 둘 중 하나라도 값이 없다면 alert
를 return하며 submit event를 종료한다.
// LoginComponent.jsx
const LoginFunc = (e) => {
e.preventDefault();
if (!id) {
return alert("ID를 입력하세요.");
}
else if (!password) {
return alert("Password를 입력하세요.");
}
...
}
🤔 Reload 막기(
e.preventDefault()
)
HTML에서 a 태그나 submit 속성은 고유의 동작으로 페이지를 이동시키거나,<form>
안에<input>
등을 전송하는 동작이 있다.
버튼의type="submit"
으로 인해 로그인 버튼을 누르면form
이 전송되며 페이지가 리로드된다. 페이지가 리로드되면 원하는 작업을 할 수 없으므로e.preventDefault();
를 사용하여 이 현상을 막았다.
ID와 Password에 모두 값이 입력되었다면 axios
통신을 시작한다.
통신 결과에 따라 유저에게 띄워줄 메시지를 state로 관리했다. (setMsg
)
로그인이 성공했을 때(code === 200)는 useDispatch
로 액션함수(loginUser
)를 호출하고, 저장할 유저 정보(name
, id
)를 액션함수의 파라미터로 전달했다.
// LoginComponent.jsx
const LoginFunc = (e) => {
...
else {
let body = {
id,
password
};
axios.post("Endpoint", body)
.then((res) => {
console.log(res.data);
if(res.data.code === 200) {
console.log("로그인");
dispatch(loginUser(res.data.userInfo));
setMsg("");
}
if(res.data.code === 400) {
setMsg("ID, Password가 비어있습니다.");
}
if(res.data.code === 401) {
setMsg("존재하지 않는 ID입니다.");
}
if(res.data.code === 402) {
setMsg("Password가 틀립니다.");
}
});
}
setLoading(true);
}
🤔 object의 key와 value가 이름이 같을 때
다음처럼 key와 value가 같을 때, 굳이 key와 value를 모두 써주지 않고,let body = { id: id, password: password };
이렇게도 쓸 수 있다!
let body = { id, password };
통신 결과에 따라 유저에게 메시지를 출력해야 하고, 통신 중에는 버튼을 다시 클릭할 수 없다. 따라서 메시지(msg
)와 버튼 disabled(loading
)는 모두 state로 관리했다.
useEffect
msg
가 출력되고 1.5초 후에 사라져야 하므로 useEffect
, setTimeout
을 사용해서 처리했다. 의존성 배열로 msg
와 loading
를 주어 msg
와 loading
이 업데이트 될 때만 실행되도록 하였다.
Msg
1.5초 후에 Msg가 사라져야 하므로 setMsg("");
loading
버튼 disabled
의 초기값을 false
로 두었다. 통신을 하는 동안과 메시지가 출력되어 있는 동안에는 로그인 버튼을 다시 클릭할 수 없으므로 axios
통신이 이루어지는 동안에는 값을 true
로 변경하였고, 1.5초 후에는 다시 false
로 변경하여 버튼을 클릭할 수 있도록 하였다.
// LoginComponent.jsx
const [loading, setLoading] = useState(false);
const [msg, setMsg] = useState("");
useEffect(() => {
if (msg) {
setTimeout(() => {
setMsg("");
setLoading(false);
}, 1500);
}
}, [msg])
...
<button type="submit" disabled={loading}>로그인</button>
axios
통신 시 로그인이 성공했을 때 액션함수 파라미터로 전송한 유저 정보를 받아 reducer 함수에서 state 값을 변경했다.
이렇게 저장된 state의 값은 MyPage.jsx
에서 userSelector
를 통해 접근하여 유저 이름(user.name
)을 출력할 수 있다.
// LoginComponent.jsx
let user = useSelector((state) => {return state.user});
const dispatch = useDispatch();
if(res.data.code === 200) {
console.log("로그인");
dispatch(loginUser(res.data.userInfo));
setMsg("");
}
// userSlice.js
export const userSlice = createSlice({
name: "user",
initialState: {
name: "",
id: "",
isLoading: false, // optional
isLogin: null,
},
reducers: {
loginUser: (state, action) => {
state.name = action.payload.name;
state.id = action.payload.id;
state.isLogin = true;
},
clearUser: (state) => {
state.name = "";
state.id = "";
state.isLogin = false;
},
},
});
// MyPage.jsx
function MyPage() {
const user = useSelector((state) => state.user);
const dispatch = useDispatch();
const LogoutFunc = () => {
console.log('로그아웃');
dispatch(clearUser());
}
return (
<>
<h1>MyPage</h1>
<p>{user.name}님, 안녕하세요!</p>
<button onClick={() => LogoutFunc()}>로그아웃</button>
</>
)
}
로그아웃 버튼을 눌렀을 때, useDispatch
를 통해 액션함수를 호출하여 유저 정보를 초기화하고, Login Component로 돌아가게 하였다.
// MyPage.jsx
const user = useSelector((state) => state.user);
const dispatch = useDispatch()
...
const LogoutFunc = () => {
console.log('로그아웃');
dispatch(clearUser());
}
...
<button onClick={() => LogoutFunc()}>로그아웃</button>
// userSlice.js
reducers: {
...
clearUser: (state) => {
state.name = "";
state.id = "";
state.isLogin = false;
},
},
isLogin
이 true
일 때는(= 로그인 되어 있을 때) <Mypage />
를 렌더링, false
일 때는(= 로그아웃 되어 있을 때) <LoginComponent />
를 렌더링한다.
// App.js
{user.isLogin ? <MyPage /> : <LoginComponent />}
axios
통신axios도 처음이고, 서버랑 통신하는 게 처음이라서 많이 헤맸다.
유저가 폼을 제출할 때만 서버에 요청을 보내면 되는데, LoginFunc
에다가 통신하는 코드를 넣지 않고 그냥 바깥쪽에 넣어버려서 input이 변경될 때마다 리렌더링이 발생했고.. 요청을 거의 1초에 10번꼴로 보내버렸다..😅
그래서 나중에 이 사실을 깨닫고 제출할 때만 요청 보내도록 통신하는 코드를 LoginFunc
에 넣었다.
처음에 useEffect
에 cleanup까지 만들었는데 불필요한 코드여서 삭제했다.
useEffect
의존성 배열처음엔 의존성 배열에 loading
을 주지 않고, msg
만 주었다.
loading
을 주지 않아도 작동은 정상적으로 하지만, useEffect
사용법에 맞도록 리팩토링하였다.
useEffect(() => {
if (msg && loading) {
setTimeout(() => {
setMsg("");
setLoading(false);
}, 1500);
}
}, [msg, loading])
switch
문)원래 코드에서는 if문으로 처리했었는데 어떤 변수 하나(res.data.code
)의 여러 값에 대해 처리하는 것이므로 switch문으로 처리하는 것이 더 적절하다고 생각되어 변경하였다.
switch (res.data.code) {
case 200:
console.log("로그인");
dispatch(loginUser(res.data.userInfo));
break;
case 400:
setMsg("ID, Password가 비어있습니다.");
break;
case 401:
setMsg("존재하지 않는 ID입니다.");
break;
case 402:
setMsg("Password가 틀립니다.");
break;
default:
break;
}