아직 익숙하지 않은 훅스 개념을 직접 코드로 쳐보면서 익혀보자. 3주 전에 만들었던 Instagram 로그인페이지를 Hooks를 사용해서 functional 컴포넌트로 바꿔보고 styled-component를 적용해서 새로 작성해보았다.
참고자료
how to access history in app.js in functional components
[React Hook] 양식(form)에 적용하기
this.state
➔ useState
class Login extends React.Component {
constructor() {
super();
this.state = {
userID: "",
userPW: "",
isBtnActive: false,
};
}
export default function Login() {
const [values, setValues] = useState({ email: "", password: "" });
const [isBtnActive, setIsBtnActive] = useState(false);
state 변수를 선언해주었다. useState의 첫번째 인자인 { email: "", password: "" }
, false
가 각각 values
, isBtnActive
의 초기값이 된다.
handleChange()
+ Validation 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="비밀번호"
/>
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란?
history.push
userLogin = (e) => {
e.preventDefault();
const { isBtnActive } = this.state;
if (isBtnActive) {
this.props.history.push("main");
}
};
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
에서 임포트해서 사용한다.
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;
@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;
}
}
}
}
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;
}
`;
빛만 따라갑니다.