[React] 로그인 구현하기

JY·2022년 5월 28일
16

React

목록 보기
1/1
post-thumbnail
  • axios를 통해 서버와 통신하여 로그인을 진행하는 로그인 컴포넌트 구현
  • Redux를 이용해 유저 정보를 state로 관리
  • Component, Form, React Hooks, Redux 다루기

기본 구조



App


Redux


Login Component

요구사항

  • 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 명세서

API 명세서

  • 반드시 body(obj)를 같이 보내줘야 함.
  • body.id : 유저 id
  • body.password : 유저 pw

  • 로그인 실패 시
    • code : 400 = body 값이 비어 있을 때
    • code : 401 = 존재하지 않는 id일 때
    • code : 402 = 비밀번호가 틀렸을 때

  • 로그인 성공 시
    • code : 200

  • userInfo = object, user의 data가 넘어 옴

Mypage Component

요구사항

  • 로그인한 유저에 따라서 다른 이름을 출력해야 합니다.
  • 로그아웃 버튼을 클릭하면 user state (redux)를 초기값으로
    되돌립니다. = 로그아웃 동작을 수행합니다.

👩‍💻 Review


로그인 성공 및 로그아웃 시

로그인 실패 시



1. ID, Password 값 관리

ID와 Password의 <input>에 값이 입력되었을 때, onChangeuseState를 통해 입력된 값을 관리했다.

// 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)}/>

2. 로그인 버튼이 눌렸을 때 처리(LoginFunc)

먼저 로그인 버튼이 눌렸을 때, input이 채워지지 않은 경우와 input이 채워진 경우 크게 두 가지로 나누어서 처리했다.

ID, Password가 입력되지 않았을 때

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가 입력되었을 때

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
};

3. 통신 결과에 따른 Messeage 처리

통신 결과에 따라 유저에게 메시지를 출력해야 하고, 통신 중에는 버튼을 다시 클릭할 수 없다. 따라서 메시지(msg)와 버튼 disabled(loading)는 모두 state로 관리했다.

useEffect

msg가 출력되고 1.5초 후에 사라져야 하므로 useEffect, setTimeout을 사용해서 처리했다. 의존성 배열로 msgloading를 주어 msgloading이 업데이트 될 때만 실행되도록 하였다.

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>

4. 로그인 성공 시

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>
    </>
  )
}

5. 로그아웃

로그아웃 버튼을 눌렀을 때, 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;
  },
},

isLogintrue일 때는(= 로그인 되어 있을 때) <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;
}

reducers

profile
🙋‍♀️

0개의 댓글