TIL 62 | 클래스형 컴포넌트인 로그인 페이지를 함수형 컴포넌트로 바꿔보자 ( Hooks & styled-component)

hyounglee·2020년 10월 4일
3

React

목록 보기
21/33

아직 익숙하지 않은 훅스 개념을 직접 코드로 쳐보면서 익혀보자. 3주 전에 만들었던 Instagram 로그인페이지를 Hooks를 사용해서 functional 컴포넌트로 바꿔보고 styled-component를 적용해서 새로 작성해보았다.

참고자료
how to access history in app.js in functional components
[React Hook] 양식(form)에 적용하기

클래스형 컴포넌트 ➔ 함수형 컴포넌트

1. this.stateuseState

As-is

class Login extends React.Component {
  constructor() {
    super();

    this.state = {
      userID: "",
      userPW: "",
      isBtnActive: false,
    };
  }

To-be

export default function Login() {
  const [values, setValues] = useState({ email: "", password: "" });
  const [isBtnActive, setIsBtnActive] = useState(false);

state 변수를 선언해주었다. useState의 첫번째 인자인 { email: "", password: "" }, false가 각각 values, isBtnActive 의 초기값이 된다.

2. handleChange() + Validation

As-is

  handleInput = (e) => {
    const { name, value } = e.target;
    this.setState({ [name]: value }, () => this.canBeSubmitted());
  };

  canBeSubmitted = () => {
    const { userID, userPW } = this.state;
    const isBtnActive = userID.includes("@") > 0 && userPW.length >= 5;

    this.setState({ isBtnActive });
  };
      ...
            <input
              value={userID}
              onChange={this.handleInput}
              className="inputLogin"
              type="text"
              name="userID"
              placeholder="전화번호, 사용자 이름 또는 이메일"
            />
            <input
              value={userPW}
              onChange={this.handleInput}
              className="inputLogin"
              type="password"
              name="userPW"
              placeholder="비밀번호"
            />

To-be

  const handleChange = (event) => {
    event.persist();
    const { name, value } = event.target;
    setValues({ ...values, [name]: value });
  };

  useEffect(() => {
    setIsBtnActive(
      values.email.includes("@") && values.password.length >= 5 ? true : false
    );
  }, [values]);
     ...
          <InputLogin
            value={values.email || ""}
            onChange={handleChange}
            type="text"
            name="email"
            placeholder="전화번호, 사용자 이름 또는 이메일"
          />
          <InputLogin
            value={values.password || ""}
            onChange={handleChange}
            type="password"
            name="password"
            placeholder="비밀번호"
          />

useEffect 를 사용하여 values 값이 변할 때 마다 validation check를 하도록했다.

참고자료: event.persist란?

3. history.push

As-is

  userLogin = (e) => {
    e.preventDefault();
    const { isBtnActive } = this.state;
    if (isBtnActive) {
      this.props.history.push("main");
    }
  };

To-be

import { useHistory } from "react-router-dom";

export default function LoginHyung() {
  const history = useHistory();
...
  const handleSubmit = (event) => {
    event.preventDefault();
    if (isBtnActive) {
      history.push("main");
    }
  };

함수형 컴포넌트에서는 this.props.history 대신에 useHistory Hook을 react-router-dom에서 임포트해서 사용한다.

SASS ➔ styled-components

As-is

Login.js

import React from "react";
import "./Login.scss";

class Login extends React.Component {
  constructor() {
    super();

    this.state = {
      userID: "",
      userPW: "",
      isBtnActive: false,
    };
  }

  handleInput = (e) => {
    const { name, value } = e.target;
    this.setState({ [name]: value }, () => this.canBeSubmitted());
  };

  canBeSubmitted = () => {
    const { userID, userPW } = this.state;
    const isBtnActive = userID.includes("@") > 0 && userPW.length >= 5;

    this.setState({ isBtnActive });
  };

  userLogin = (e) => {
    e.preventDefault();
    const { isBtnActive } = this.state;
    if (isBtnActive) {
      this.props.history.push("main");
    }
  };

  render() {
    const { userID, userPW } = this.props;
    const { isBtnActive } = this.state;
    return (
      <div className="Login">
        <div className="loginContainer">
          <img
            alt="instagram_logo"
            className="logoInstagram"
            src="/img/logo_text.png"
          />
          <form onSubmit={this.userLogin} className="loginInputForm">
            <input
              value={userID}
              onChange={this.handleInput}
              className="inputLogin"
              type="text"
              name="userID"
              placeholder="전화번호, 사용자 이름 또는 이메일"
            />
            <input
              value={userPW}
              onChange={this.handleInput}
              className="inputLogin"
              type="password"
              name="userPW"
              placeholder="비밀번호"
            />
            <button
              onClick={this.userLogin}
              type="submit"
              className="btnLogin"
              disabled={!isBtnActive}
            >
              로그인
            </button>
          </form>
          <span className="btnForgot">비밀번호를 잊으셨나요?</span>
        </div>
      </div>
    );
  }
}

export default Login;

Login.scss

@import "../../../Styles/variables.scss";

.Login {
    @include flexBox(center, center);
    width: 100vw;
    height: 100vh;
    p,
    span {
        font-size: 14px;
    }
    div {
        @include flexBox(default, center);
        @include defaultBox($white);
        width: 350px;
        height: 380px;
        flex-direction: column;
        flex-wrap: wrap;
        img {
            align-self: center;
            width: 175px;
            height: auto;
            margin: 35px 0;
        }
        .loginInputForm {
            display: inline-block;
        }
        .inputLogin {
            @include defaultBox($background);
            width: 270px;
            height: 40px;
            margin: 6px auto;
            padding: 0 10px;
            display: block;
            font-size: 14px;
            &::placeholder {
                font-size: 12px;
                color: $subtext;
            }
            &:focus {
                border: 1px solid #8a8a8a;
                outline: none;
            }
        }
        button {
            width: 268px;
            height: 30px;
            margin: 13px 0;
            background-color: $enabled;
            border: none;
            border-radius: 3px;
            color: $white;
            font-size: 14px;
            font-weight: 600;
            &:disabled {
                background-color: $disabled;
                &:hover {
                    cursor: default;
                }
            }
            &:enabled:hover {
                cursor: pointer;
            }
            &:focus {
                outline: none;
            }
        }
        span {
            color: #00366b;
            align-self: center;
            font-size: 12px;
            margin-top: 65px;
            &:hover {
                cursor: pointer;
            }
        }
    }
}

To-be

Login.js

import React, { useState, useEffect } from "react";
import styled, { css } from "styled-components";
import { useHistory } from "react-router-dom";

export default function Login() {
  const [values, setValues] = useState({ email: "", password: "" });
  const [isBtnActive, setIsBtnActive] = useState(false);
  const history = useHistory();

  const handleChange = (event) => {
    event.persist();
    const { name, value } = event.target;
    setValues({ ...values, [name]: value });
  };

  useEffect(() => {
    setIsBtnActive(
      values.email.includes("@") && values.password.length >= 5 ? true : false
    );
  }, [values]);

  const handleSubmit = (event) => {
    event.preventDefault();
    if (isBtnActive) {
      history.push("main");
    }
  };

  return (
    <LoginPage>
      <LoginContainer>
        <LogoImg />
        <LoginInputForm onSubmit={handleSubmit}>
          <InputLogin
            value={values.email || ""}
            onChange={handleChange}
            type="text"
            name="email"
            placeholder="전화번호, 사용자 이름 또는 이메일"
          />
          <InputLogin
            value={values.password || ""}
            onChange={handleChange}
            type="password"
            name="password"
            placeholder="비밀번호"
          />
          <Button onClick={handleSubmit} disabled={!isBtnActive}>
            로그인
          </Button>
        </LoginInputForm>
        <BtnForgot>비밀번호를 잊으셨나요?</BtnForgot>
      </LoginContainer>
    </LoginPage>
  );
}

const defaultBox = (color) => css`
  background-color: ${color || "white"};
  border-radius: 3px;
  border: 1px solid #dbdbdb;
`;

const flexBox = (justify, align) => css`
  display: flex;
  justify-content: ${justify || "default"};
  align-items: ${align || "default"};
`;

const LoginPage = styled.div`
  ${flexBox("center", "center")};
  width: 100vw;
  height: 100vh;
  p,
  span {
    font-size: 14px;
  }
`;

const LoginContainer = styled.div`
  ${flexBox("default", "center")};
  width: 350px;
  height: 380px;
  flex-direction: column;
  flex-wrap: wrap;
  ${defaultBox};
  background-color: white;
  img {
    align-self: center;
    width: 175px;
    height: auto;
    margin: 35px 0;
  }
`;

const LoginInputForm = styled.form`
  display: inline-block;
`;

const LogoImg = styled.img.attrs(() => ({
  alt: "instagram_logo",
  src: "/img/logo_text.png",
}))`
  align-self: center;
  width: 175px;
  height: auto;
  margin: 35px 0;
`;

const InputLogin = styled.input`
  ${defaultBox("#fafafa")};
  display: block;
  width: 270px;
  height: 40px;
  margin: 6px auto;
  padding: 0 10px;
  font-size: 14px;
  &::placeholder {
    font-size: 12px;
    color: #8e8e8e;
  }
  &:focus {
    border: 1px solid #8a8a8a;
    outline: none;
  }
`;

const Button = styled.button.attrs(() => ({
  type: "submit",
}))`
  width: 268px;
  height: 30px;
  margin: 13px 0;
  background-color: #0095f6;
  border: none;
  border-radius: 3px;
  color: white;
  font-size: 14px;
  font-weight: 600;

  &:disabled {
    background-color: #b9dffc;

    &:hover {
      cursor: default;
    }
  }

  &:enabled:hover {
    cursor: pointer;
  }

  &:focus {
    outline: none;
  }
`;

const BtnForgot = styled.span`
  color: #00366b;
  align-self: center;
  font-size: 12px;
  margin-top: 65px;
  &:hover {
    cursor: pointer;
  }
`;
profile
(~˘▾˘)~♫❝ 쉽게만 살아가면 재미없어 빙고 .ᐟ ❞•*¨*•.¸¸♪

2개의 댓글

comment-user-thumbnail
2020년 10월 5일

빛만 따라갑니다.

1개의 답글