ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -1,5 +1,98 @@
 import React from 'react';
+import { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import './Login.scss';
 
-export default function Login() {
-  return <div>Hello, Hyeonze!</div>;
-}
+const Login = () => {
+  const [idInput, setIdInput] = useState('');
+  const [pwInput, setPwInput] = useState('');
+  const [isValidatedUser, setIsValidatedUser] = useState(false);
+
+  const navigate = useNavigate();
+  const changeIdInput = e => {
+    const { value } = e.target;
+    setIdInput(value);
+  };
+
+  const changePwInput = e => {
+    const { value } = e.target;
+    setPwInput(value);
+  };
+
+  useEffect(() => {
+    setIsValidatedUser(idInput.indexOf('@') !== -1 && pwInput.length > 4);
+  }, [idInput, pwInput]);
+
+  // 메인으로 이동할 때 로직
+  const goToMain = () => {
+    navigate('/main-hyeonze');
+  };
+
+  // 회원가입시 사용할 로직
+  // const signup = () => {
+  //   fetch('http://10.58.4.22:8000/users/signup', {
+  //     method: 'POST',
+  //     body: JSON.stringify({
+  //       email: idInput,
+  //       password: pwInput,
+  //     }),
+  //   })
+  //     .then(response => response.json())
+  //     .then(result => {
+  //       //console.log('결과: ', result.message)
+  //       if (result.message === 'EMAIL_ALREADY_EXISTS') {
+  //         console.log('Error : Duplicated');
+  //       }
+  //     });
+  // };
+
+  // 로그인시 사용할 로직
+  // const login = () => {
+  //   fetch('http://10.58.4.22:8000/users/login', {
+  //     method: 'POST',
+  //     body: JSON.stringify({
+  //       email: idInput,
+  //       password: pwInput,
+  //     }),
+  //   })
+  //     .then(response => response.json())
+  //     .then(result => {
+  //       //console.log('결과: ', result.message)
+  //       if (result.message === 'SUCCESS') {
+  //         console.log('login SUCCESS');
+  //       }
+  //     });
+  // };
+
+  return (
+    <div className="login">
+      <div id="wrapper">
+        <h1>westagram</h1>
+        <form className="login_form">
+          <input
+            type="text"
+            placeholder="전화번호, 사용자 이름 또는 이메일"
+            className="username"
+            onChange={changeIdInput}
+          />
+          <input
+            type="password"
+            placeholder="비밀번호"
+            className="userpassword"
+            onChange={changePwInput}
+          />
+          <input
+            className={isValidatedUser ? 'activated' : ''}
+            disabled={!isValidatedUser}
+            type="button"
+            value="로그인"
+            onClick={goToMain}
+          />
+        </form>
+        <p>비밀번호를 잊으셨나요?</p>
+      </div>
+    </div>
+  );
+};
+
+export default Login;

리뷰: 이 두 조건을 &&로 걸면 state를 하나만 사용하고 validation을 확인할 수 있지 않을까요? 
왜 유즈이펙트를 쓰셨는지 궁금합니다.
@@ -1,5 +1,98 @@
 import React from 'react';
+import { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import './Login.scss';
 
-export default function Login() {
-  return <div>Hello, Hyeonze!</div>;
-}
+const Login = () => {
+  const [idInput, setIdInput] = useState('');
+  const [pwInput, setPwInput] = useState('');
+  const [isValidatedUser, setIsValidatedUser] = useState(false);
+
+  const navigate = useNavigate();
+  const changeIdInput = e => {
+    const { value } = e.target;
+    setIdInput(value);
+  };
+
+  const changePwInput = e => {
+    const { value } = e.target;
+    setPwInput(value);
+  };
+
+  useEffect(() => {
+    setIsValidatedUser(idInput.indexOf('@') !== -1 && pwInput.length > 4);
+  }, [idInput, pwInput]);
+
+  // 메인으로 이동할 때 로직
+  const goToMain = () => {
+    navigate('/main-hyeonze');
+  };
+
+  // 회원가입시 사용할 로직
+  // const signup = () => {
+  //   fetch('http://10.58.4.22:8000/users/signup', {
+  //     method: 'POST',
+  //     body: JSON.stringify({
+  //       email: idInput,
+  //       password: pwInput,
+  //     }),
+  //   })
+  //     .then(response => response.json())
+  //     .then(result => {
+  //       //console.log('결과: ', result.message)
+  //       if (result.message === 'EMAIL_ALREADY_EXISTS') {
+  //         console.log('Error : Duplicated');
+  //       }
+  //     });
+  // };
+
+  // 로그인시 사용할 로직
+  // const login = () => {
+  //   fetch('http://10.58.4.22:8000/users/login', {
+  //     method: 'POST',
+  //     body: JSON.stringify({
+  //       email: idInput,
+  //       password: pwInput,
+  //     }),
+  //   })
+  //     .then(response => response.json())
+  //     .then(result => {
+  //       //console.log('결과: ', result.message)
+  //       if (result.message === 'SUCCESS') {
+  //         console.log('login SUCCESS');
+  //       }
+  //     });
+  // };
+
+  return (
+    <div className="login">
+      <div id="wrapper">
+        <h1>westagram</h1>
+        <form className="login_form">
+          <input
+            type="text"
+            placeholder="전화번호, 사용자 이름 또는 이메일"
+            className="username"
+            onChange={changeIdInput}
+          />
+          <input
+            type="password"
+            placeholder="비밀번호"
+            className="userpassword"
+            onChange={changePwInput}
+          />
+          <input
+            className={isValidatedUser ? 'activated' : ''}
+            disabled={!isValidatedUser}
+            type="button"
+            value="로그인"
+            onClick={goToMain}
+          />
+        </form>
+        <p>비밀번호를 잊으셨나요?</p>
+      </div>
+    </div>
+  );
+};
+
+export default Login;

리뷰: id와 pw로 한번에 validation하고 activateBtn에 True/false를 지정하면 될 것 같습니다. isValidatedId나 isValidatedPw는 크게 필요하지 않을 것 같아요. 불필요한 리랜더링도 줄일 수 있다고 생각하는데 어떻게 생각하시나요?
@@ -0,0 +1,66 @@
+@use '../../../styles/mixin.scss' as mixin;
+@use '../../../styles/color.scss' as clr;
+
+body {
+  background: clr.$bg-login;
+
+  .login {
+    position: relative;
+    top: -80px;
+
+    #wrapper {
+      @include mixin.flex($align: center);
+      position: relative;
+      flex-direction: column;
+      width: 700px;
+      height: 761px;
+      border: 1px solid clr.$clr-border;
+
+      h1 {
+        margin-top: 90px;
+        margin-bottom: 130px;
+        font-size: 81px;
+        font-family: 'Lobster', cursive;
+      }
+
+      .login_form {
+        input {
+          display: block;
+          width: 536px;
+          height: 76px;
+          margin-bottom: 12px;
+          padding-left: 10px;
+          background: #fafafa;
+          border: 1px solid clr.$clr-border;
+          border-radius: 3px;
+          font-size: 16px;
+        }
+
+        input[type='button'] {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          width: 536px;
+          height: 60px;
+          margin-top: 16px;
+          background-color: clr.$clr-primary;
+          opacity: 0.5;
+          border: none;
+          border-radius: 4px;
+          font-size: 16px;
+          color: #fff;
+
+          &.activated {
+            opacity: 1;
+          }
+        }
+      }
+
+      p {
+        position: absolute;
+        bottom: 20px;
+        color: #043667;
+      }
+    }
+  }
+}

리뷰: input이 inline-block이고 수직으로 정렬하려고 display: flex를 쓰신 것 같습니다. 굉장히 좋은데요 저는 Input을 display: block해주는 것도 괜찮은 방법이지 않을까 생각합니다.
@@ -0,0 +1,66 @@
+@use '../../../styles/mixin.scss' as mixin;
+@use '../../../styles/color.scss' as clr;
+
+body {
+  background: clr.$bg-login;
+
+  .login {
+    position: relative;
+    top: -80px;
+
+    #wrapper {
+      @include mixin.flex($align: center);
+      position: relative;
+      flex-direction: column;
+      width: 700px;
+      height: 761px;
+      border: 1px solid clr.$clr-border;
+
+      h1 {
+        margin-top: 90px;
+        margin-bottom: 130px;
+        font-size: 81px;
+        font-family: 'Lobster', cursive;
+      }
+
+      .login_form {
+        input {
+          display: block;
+          width: 536px;
+          height: 76px;
+          margin-bottom: 12px;
+          padding-left: 10px;
+          background: #fafafa;
+          border: 1px solid clr.$clr-border;
+          border-radius: 3px;
+          font-size: 16px;
+        }
+
+        input[type='button'] {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          width: 536px;
+          height: 60px;
+          margin-top: 16px;
+          background-color: clr.$clr-primary;
+          opacity: 0.5;
+          border: none;
+          border-radius: 4px;
+          font-size: 16px;
+          color: #fff;
+
+          &.activated {
+            opacity: 1;
+          }
+        }
+      }
+
+      p {
+        position: absolute;
+        bottom: 20px;
+        color: #043667;
+      }
+    }
+  }
+}

리뷰: '로그인' 이라는 글자 센터에 맞추기 위해 display: flex주신 것 같아요. 좋은방법이지만 flex와 그 속성들이 좀 떨어져 있는 것 같습니다. 이렇게 연관되는 css속성은 붙여 주시는 게 나중에 유지보수 측면에서도 좋을 것 같아 추천 드립니다!
@@ -0,0 +1,26 @@
+import React, { useState } from 'react';
+import { AiFillDelete, AiOutlineHeart, AiFillHeart } from 'react-icons/ai';
+
+const Comment = ({ id, userId, value, time, handleDelete }) => {
+  const [isLiked, setIsLiked] = useState(false);
+
+  const toggleLike = () => {
+    setIsLiked(!isLiked);
+  };
+
+  return (
+    <span id={id} className="comment">
+      <strong>{userId} </strong>
+      {value}
+      <small> {time}</small>
+      <AiFillDelete onClick={handleDelete} className="delete" />
+      {isLiked ? (
+        <AiFillHeart onClick={toggleLike} className="hearts liked" />
+      ) : (
+        <AiOutlineHeart onClick={toggleLike} className="hearts" />
+      )}
+    </span>
+  );
+};
+
+export default Comment;

리뷰: 저번 세션에서 멘토님이 하신 것처럼 변수명을 조금 더 구체적으로 어떤 내용을 담고 있는지를 작성해 주시면 좋을 것 같습니다. commentVal이 댓글 전체를 관리하는 배열이고 el이 각 배열의 요소이니깐 
commentVal대신 allComments, el대신 comment등으로 (이건 저도 조금 애매하네요 .. 죄송합니다) (배열은 s를 붙인 변수명으로 하는 게 좋을 것 같습니다!)
@@ -0,0 +1,43 @@
+span.comment {
+  padding-left: 10px;
+
+  small {
+    color: #a3a3a3;
+  }
+
+  &:first-child {
+    padding: 10px;
+    font-weight: bold;
+  }
+
+  svg {
+    &.delete {
+      display: none;
+      cursor: pointer;
+    }
+
+    &.hearts {
+      color: #a3a3a3;
+      position: absolute;
+      right: 20px;
+      transition: color 1s;
+      cursor: pointer;
+
+      path {
+        color: #a3a3a3;
+      }
+    }
+
+    &.liked {
+      path {
+        color: #f00;
+      }
+    }
+  }
+
+  &:hover svg {
+    &.delete {
+      display: inline-block;
+    }
+  }
+}

리뷰: span에 대해서만 스타일링을 해주셨는데 이러면 다른 컴포넌트의 span에도 영향이 가지 않을까 조심스럽게 생각해 봅니다. 개인적으로 className을 주는 게 조금 더 안전할 것 같습니다

 추가적으로 svg태그도 span 내부에 nesting해주시면 좋을 것 같아요.
```
svg {
   &.red {} 
   &.blue {} 
} 
```
@@ -0,0 +1,87 @@
+import React, { useState } from 'react';
+import { AiFillHeart, AiOutlineComment, AiOutlineUpload } from 'react-icons/ai';
+import { BiDotsHorizontalRounded } from 'react-icons/bi';
+import { GrBookmark } from 'react-icons/gr';
+import './Feeds.scss';
+import Comments from './Comment/Comments';
+
+export default function Feeds({ userId, profileImg, img, comments }) {
+  const [commentVal, setCommentVal] = useState(comments);
+  let addedCommentVal = [...commentVal];
+  const [currInputVal, setCurrInputVal] = useState('');
+
+  const handleInput = e => {
+    const { value } = e.target;
+    if (e.keyCode === 13) uploadComment();
+    setCurrInputVal(value);
+  };
+
+  const uploadComment = e => {
+    e.preventDefault();
+    if (!currInputVal) return;
+    addedCommentVal.push({
+      id: addedCommentVal.length + 1,
+      userId: 'userid',
+      value: currInputVal,
+      time: '방금전',
+    });
+    setCommentVal(addedCommentVal);
+    setCurrInputVal('');
+  };
+
+  const handleDelete = e => {
+    const { id } = e.target.parentNode.parentNode;
+    addedCommentVal = addedCommentVal.filter(el => el.id !== Number(id));
+    setCommentVal(addedCommentVal);
+  };
+
+  return (
+    <div className="feeds">
+      <article>
+        <div className="top_menu">
+          <span>
+            <img src={profileImg} alt="프로필" />
+            {userId}
+          </span>
+          <span>
+            <BiDotsHorizontalRounded />
+          </span>
+        </div>
+        <img src={img} alt="무화과" />
+        <div className="bottom_menu">
+          <span>
+            <AiFillHeart className="" />
+          </span>
+          <span>
+            <AiOutlineComment className="" />
+          </span>
+          <span>
+            <AiOutlineUpload className="" />
+          </span>
+          <span>
+            <GrBookmark className="" />
+          </span>
+        </div>
+        <div className="comments">
+          <span>4 Likes</span>
+          <Comments commentArr={commentVal} handleDelete={handleDelete} />
+        </div>
+        <form className="add_comments">
+          <input
+            type="text"
+            placeholder="댓글 달기..."
+            className="input_upload"
+            onChange={handleInput}
+            value={currInputVal}
+          />
+          <button
+            className={currInputVal ? 'activated' : ''}
+            onClick={uploadComment}
+          >
+            게시
+          </button>
+        </form>
+      </article>
+    </div>
+  );
+}

리뷰: 굉장히 잘 짜신 것 같습니다!! 👏 그런데 순서가 조금 애매한 것 같아요. 17)에서 uploadComment 호출을 하고 31)에서 currInputVal( " " ) -> 18 ) 에서 currInputVal( e.target.value ) 순으로 진행이 된다면 댓글을 추가하더라도 input은 비워지지 않을 것 같습니다.
@@ -0,0 +1,101 @@
+@use '../../../../styles/mixin.scss' as mixin;
+@use '../../../../styles/color.scss' as clr;
+
+.feeds {
+  position: relative;
+  top: 77px;
+  width: 60%;
+  margin-bottom: 80px;
+  background: #fff;
+
+  .top_menu {
+    @include mixin.flex($justify: space-between, $align: center);
+    height: 82px;
+
+    span {
+      @include mixin.flex($align: center);
+      margin-left: 20px;
+      font-weight: bold;
+
+      &:last-child {
+        margin-right: 22px;
+        color: #2a2a2a;
+        font-size: 35px;
+      }
+
+      img {
+        width: 42px;
+        height: 42px;
+        border-radius: 50%;
+        margin-right: 20px;
+      }
+    }
+  }
+
+  article {
+    img {
+      width: 100%;
+      height: 818px;
+    }
+
+    .bottom_menu {
+      @include mixin.flex($align: center);
+      position: relative;
+      height: 62px;
+
+      span > svg {
+        margin: 0 10px;
+        font-size: 32px;
+        color: #414141;
+      }
+
+      span:last-child > svg {
+        position: absolute;
+        top: 15px;
+        right: 0;
+      }
+
+      span:first-child > svg > path {
+        color: #eb4b59;
+      }
+    }
+  }
+
+  .comments {
+    display: flex;
+    flex-direction: column;
+
+    span {
+      padding-left: 10px;
+    }
+  }
+
+  .add_comments {
+    position: absolute;
+    @include mixin.flex($justify: space-between, $align: center);
+    bottom: -62px;
+    width: 100%;
+    height: 62px;
+    border-top: 1px solid clr.$clr-border;
+    font-size: 14px;
+
+    input {
+      width: 90%;
+      height: 50%;
+      margin-left: 15px;
+      border: none;
+    }
+
+    button {
+      margin-right: 15px;
+      border: none;
+      color: clr.$clr-primary;
+      opacity: 0.5;
+      background: #fff;
+
+      &.activated {
+        opacity: 1;
+      }
+    }
+  }
+}

리뷰: Feeds.jsx에서 .feeds의 바로 자식요소가 article이라 article을 .feeds 바로 다음에 써주는 게 좋을 것 같습니다. 그리고 height요소는 자식요소들의 높이에 따라 결정되니깐 height: 100%를 주어도 feeds의 높이가 변하지는 않을 것 같다는 생각이 드는데 혹시 설정하신 이유가 있으신가요?
@@ -1,5 +1,98 @@
 import React from 'react';
+import { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import './Login.scss';
 
-export default function Login() {
-  return <div>Hello, Hyeonze!</div>;
-}
+const Login = () => {
+  const [idInput, setIdInput] = useState('');
+  const [pwInput, setPwInput] = useState('');
+  const [isValidatedUser, setIsValidatedUser] = useState(false);
+
+  const navigate = useNavigate();
+  const changeIdInput = e => {
+    const { value } = e.target;
+    setIdInput(value);
+  };
+
+  const changePwInput = e => {
+    const { value } = e.target;
+    setPwInput(value);
+  };
+
+  useEffect(() => {
+    setIsValidatedUser(idInput.indexOf('@') !== -1 && pwInput.length > 4);
+  }, [idInput, pwInput]);
+
+  // 메인으로 이동할 때 로직
+  const goToMain = () => {
+    navigate('/main-hyeonze');
+  };
+
+  // 회원가입시 사용할 로직
+  // const signup = () => {
+  //   fetch('http://10.58.4.22:8000/users/signup', {
+  //     method: 'POST',
+  //     body: JSON.stringify({
+  //       email: idInput,
+  //       password: pwInput,
+  //     }),
+  //   })
+  //     .then(response => response.json())
+  //     .then(result => {
+  //       //console.log('결과: ', result.message)
+  //       if (result.message === 'EMAIL_ALREADY_EXISTS') {
+  //         console.log('Error : Duplicated');
+  //       }
+  //     });
+  // };
+
+  // 로그인시 사용할 로직
+  // const login = () => {
+  //   fetch('http://10.58.4.22:8000/users/login', {
+  //     method: 'POST',
+  //     body: JSON.stringify({
+  //       email: idInput,
+  //       password: pwInput,
+  //     }),
+  //   })
+  //     .then(response => response.json())
+  //     .then(result => {
+  //       //console.log('결과: ', result.message)
+  //       if (result.message === 'SUCCESS') {
+  //         console.log('login SUCCESS');
+  //       }
+  //     });
+  // };
+
+  return (
+    <div className="login">
+      <div id="wrapper">
+        <h1>westagram</h1>
+        <form className="login_form">
+          <input
+            type="text"
+            placeholder="전화번호, 사용자 이름 또는 이메일"
+            className="username"
+            onChange={changeIdInput}
+          />
+          <input
+            type="password"
+            placeholder="비밀번호"
+            className="userpassword"
+            onChange={changePwInput}
+          />
+          <input
+            className={isValidatedUser ? 'activated' : ''}
+            disabled={!isValidatedUser}
+            type="button"
+            value="로그인"
+            onClick={goToMain}
+          />
+        </form>
+        <p>비밀번호를 잊으셨나요?</p>
+      </div>
+    </div>
+  );
+};
+
+export default Login;

리뷰: 이 두 조건을 &&로 걸면 state를 하나만 사용하고 validation을 확인할 수 있지 않을까요? => 맞는 것 같습니다.

수정 전 코드에서 유효성검사를 changeIdInput, changePwInput 내에서 각각 해줬었는데 호환성 검사에 딜레이가 생겨서 useEffect를 사용하는게 해결책이라 생각했습니다. 이 과정에서 changeIdInput, changePwInput 내부의 삼항연산자들을 useEffect 내부로 잘라내어 오게 되면서 로직이 길어진 것 같습니다.
@@ -1,5 +1,98 @@
 import React from 'react';
+import { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import './Login.scss';
 
-export default function Login() {
-  return <div>Hello, Hyeonze!</div>;
-}
+const Login = () => {
+  const [idInput, setIdInput] = useState('');
+  const [pwInput, setPwInput] = useState('');
+  const [isValidatedUser, setIsValidatedUser] = useState(false);
+
+  const navigate = useNavigate();
+  const changeIdInput = e => {
+    const { value } = e.target;
+    setIdInput(value);
+  };
+
+  const changePwInput = e => {
+    const { value } = e.target;
+    setPwInput(value);
+  };
+
+  useEffect(() => {
+    setIsValidatedUser(idInput.indexOf('@') !== -1 && pwInput.length > 4);
+  }, [idInput, pwInput]);
+
+  // 메인으로 이동할 때 로직
+  const goToMain = () => {
+    navigate('/main-hyeonze');
+  };
+
+  // 회원가입시 사용할 로직
+  // const signup = () => {
+  //   fetch('http://10.58.4.22:8000/users/signup', {
+  //     method: 'POST',
+  //     body: JSON.stringify({
+  //       email: idInput,
+  //       password: pwInput,
+  //     }),
+  //   })
+  //     .then(response => response.json())
+  //     .then(result => {
+  //       //console.log('결과: ', result.message)
+  //       if (result.message === 'EMAIL_ALREADY_EXISTS') {
+  //         console.log('Error : Duplicated');
+  //       }
+  //     });
+  // };
+
+  // 로그인시 사용할 로직
+  // const login = () => {
+  //   fetch('http://10.58.4.22:8000/users/login', {
+  //     method: 'POST',
+  //     body: JSON.stringify({
+  //       email: idInput,
+  //       password: pwInput,
+  //     }),
+  //   })
+  //     .then(response => response.json())
+  //     .then(result => {
+  //       //console.log('결과: ', result.message)
+  //       if (result.message === 'SUCCESS') {
+  //         console.log('login SUCCESS');
+  //       }
+  //     });
+  // };
+
+  return (
+    <div className="login">
+      <div id="wrapper">
+        <h1>westagram</h1>
+        <form className="login_form">
+          <input
+            type="text"
+            placeholder="전화번호, 사용자 이름 또는 이메일"
+            className="username"
+            onChange={changeIdInput}
+          />
+          <input
+            type="password"
+            placeholder="비밀번호"
+            className="userpassword"
+            onChange={changePwInput}
+          />
+          <input
+            className={isValidatedUser ? 'activated' : ''}
+            disabled={!isValidatedUser}
+            type="button"
+            value="로그인"
+            onClick={goToMain}
+          />
+        </form>
+        <p>비밀번호를 잊으셨나요?</p>
+      </div>
+    </div>
+  );
+};
+
+export default Login;

리뷰: [공식문서 React로 사고하기](https://ko.reactjs.org/docs/thinking-in-react.html#step-3-identify-the-minimal-but-complete-representation-of-ui-state) 를 보시면 어떤 값들이 state가 되어야하는지에 대해 적혀있습니다. 각각 살펴보고 해당 데이터는 state로 적절한지 댓글로 남겨주세요. 각 데이터에 대해 아래의 세 가지 질문을 통해 결정할 수 있습니다

> 1. 부모로부터 props를 통해 전달됩니까? 그러면 확실히 state가 아닙니다.
> 

> 2. 시간이 지나도 변하지 않나요? 그러면 확실히 state가 아닙니다.
> 

> 3. 컴포넌트 안의 다른 state나 props를 가지고 계산 가능한가요? 그렇다면 state가 아닙니다.
>
@@ -1,5 +1,98 @@
 import React from 'react';
+import { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import './Login.scss';
 
-export default function Login() {
-  return <div>Hello, Hyeonze!</div>;
-}
+const Login = () => {
+  const [idInput, setIdInput] = useState('');
+  const [pwInput, setPwInput] = useState('');
+  const [isValidatedUser, setIsValidatedUser] = useState(false);
+
+  const navigate = useNavigate();
+  const changeIdInput = e => {
+    const { value } = e.target;
+    setIdInput(value);
+  };
+
+  const changePwInput = e => {
+    const { value } = e.target;
+    setPwInput(value);
+  };
+
+  useEffect(() => {
+    setIsValidatedUser(idInput.indexOf('@') !== -1 && pwInput.length > 4);
+  }, [idInput, pwInput]);
+
+  // 메인으로 이동할 때 로직
+  const goToMain = () => {
+    navigate('/main-hyeonze');
+  };
+
+  // 회원가입시 사용할 로직
+  // const signup = () => {
+  //   fetch('http://10.58.4.22:8000/users/signup', {
+  //     method: 'POST',
+  //     body: JSON.stringify({
+  //       email: idInput,
+  //       password: pwInput,
+  //     }),
+  //   })
+  //     .then(response => response.json())
+  //     .then(result => {
+  //       //console.log('결과: ', result.message)
+  //       if (result.message === 'EMAIL_ALREADY_EXISTS') {
+  //         console.log('Error : Duplicated');
+  //       }
+  //     });
+  // };
+
+  // 로그인시 사용할 로직
+  // const login = () => {
+  //   fetch('http://10.58.4.22:8000/users/login', {
+  //     method: 'POST',
+  //     body: JSON.stringify({
+  //       email: idInput,
+  //       password: pwInput,
+  //     }),
+  //   })
+  //     .then(response => response.json())
+  //     .then(result => {
+  //       //console.log('결과: ', result.message)
+  //       if (result.message === 'SUCCESS') {
+  //         console.log('login SUCCESS');
+  //       }
+  //     });
+  // };
+
+  return (
+    <div className="login">
+      <div id="wrapper">
+        <h1>westagram</h1>
+        <form className="login_form">
+          <input
+            type="text"
+            placeholder="전화번호, 사용자 이름 또는 이메일"
+            className="username"
+            onChange={changeIdInput}
+          />
+          <input
+            type="password"
+            placeholder="비밀번호"
+            className="userpassword"
+            onChange={changePwInput}
+          />
+          <input
+            className={isValidatedUser ? 'activated' : ''}
+            disabled={!isValidatedUser}
+            type="button"
+            value="로그인"
+            onClick={goToMain}
+          />
+        </form>
+        <p>비밀번호를 잊으셨나요?</p>
+      </div>
+    </div>
+  );
+};
+
+export default Login;

리뷰: 라이브리뷰 때 말씀드린 것처럼 조건의 falsy, truthy한 값을 이용해 리팩토링 해주세요! :) 
또는 state를 줄일 경우 불필요할 수도 있겠네요