잊고싶지 않은 코드들을 기록해보는 시간✨
로그인 페이지와 회원가입 페이지에서 공통적으로 사용하는 유효성 검증 로직을 custom hook으로 빼내어 별도로 관리해주었다.
이렇게 함으로써, 각 페이지 component의 용량을 줄일 수 있고, 유효성 관련된 조작을 한 페이지에서 할 수 있어 관리가 용이해졌다.
아래의 useForm은 form에 입력되는 모든 값을 controll하는 hook이다.
이 hook을 통해서 입력된 값들을 error validation함수로 조건부 전달이 가능하다.
import { useEffect, useState } from 'react';
// 모든 입력란의 초기값, 제출된 입력값을 처리하는 로직, 입력값을 검증하는 로직을 인자로 받습니다.
function useForm({ initialValues, onSubmit, validate }) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({}); //틀린 내용 알려주는 것
const [submitting, setSubmitting] = useState(false);
const handleChange = e => {
const { name, value } = e.target;
setValues({ ...values, [name]: value });
};
// 제출 이벤트 처리
const handleSubmit = e => {
setSubmitting(true);
e.preventDefault();
setErrors(validate(values));
};
// 에러가 없을 때만 인자로 넘어온 입력값을 처리
useEffect(() => {
if (submitting) {
if (Object.keys(errors).length === 0) {
onSubmit(values);
}
setSubmitting(false);
}
}, [errors]);
// return을 해야 이 hook을 사용하는 함수에서 활용할 수 있다!
return { values, errors, submitting, handleChange, handleSubmit };
}
export default useForm;
입력된 값들의 유효성 검증만 진행하는 validate 함수를 만들어서, 이곳에서 유효성 검증 처리 후 결과를 반환하도록 하였다.
여기선 error가 발생한 key와 error 내용을 객체화하여 form component가 있는 곳으로 전달해준다.
해당 객체에 내용이 들어있으면 form component에서 error를 대응하도록 해준다.
export default function validate({
email,
password,
}) {
const errors = {};
const num = /[0-9]/;
const char = /[a-zA-Z]/;
const pattern = /[~!@#$%^&*()-=_+]/;
if (!email) {
errors.email = '이메일을 입력해주세요';
} else if (!email.includes('@') && !email.includes('.')) {
errors.email = '올바른 양식으로 입력해주세요';
}
if (!password) {
errors.password = '비밀번호를 입력해주세요';
} else if (
!num.test(password) ||
!char.test(password) ||
!pattern.test(password) ||
password.length < 8
) {
errors.password = '8자리 이상, 문자, 숫자, 특수문자를 포함시켜주세요';
}
return errors;
}
(아래는 연결된 component에서 error 객체를 처리하는 방법이다)
return (
<Wrapper>
<Section onSubmit={handleSubmit}>
<Form onSubmit={handleSubmit}>
<InputWrapper>
<InputSet>
<input
value={values.email}
onChange={handleChange}
/>
{errors.email && <Warning>{errors.email}</Warning>}
</InputSet>
<InputSet>
<input
value={values.password}
onChange={handleChange}
/>
{errors.password && <Warning>{errors.password</Warning>}
</Section>
</Wrapper>
);
실무에서 axios를 주로 활용하여 네트워크 통신을 한다는 것을 감안하여, 이번 프로젝트에서는 fetch
함수 대신 axios
를 적용해보았다.
네트워크 통신과 관련된 로직은 data-fetch.js
라는 함수를 별도로 만들어 한곳에서 관리되도록 하였다.
import axios from 'axios';
import URL from '../config';
class fetchData {
constructor() {
}
async logIn(email, password) {
const response = await axios.post(`${URL}/users/signin`, {
email: email,
password: password,
});
const result = response.data;
return result;
}
async kakaoLogin(authObj) {
const HEADERS = {
'Content-Type': 'application/json',
Authorization: authObj.access_token,
};
const response = await axios.post(`${URL}/users/signin/kakao`, null, {
headers: HEADERS,
});
const result = response.data;
return result;
}
}
export default fetchData;
axios
는 fetch와 다르게 response, result 부분을 모두 변수화 해서 사용할 수 있기에 보다 직관적으로 코드가 눈에 들어온다는 장점이 있었다.
또한 함수로 이루어져있기에, 네트워크 통신에 필요한 매개변수들도 보다 간편하게 전달이 가능한 것 같다는 인상을 받았다.
async kakaoLogin(authObj) {
const HEADERS = {
'Content-Type': 'application/json',
Authorization: authObj.access_token,
};
const response = await axios.post(`${URL}/users/signin/kakao`, null, {
headers: HEADERS,
});
const result = response.data;
return result;
}
axios
의 경우 .post
와 같은 메서드 뒤에 아래와 같은 parameter를 기재하여야 한다.
1. URI 주소
2. body 객체 (필수 입력. 전달할 body가 없을 시 null
로 표기)
3. 그 외 전달하고픈 객체(config, headers 등)
headers 작성 시 유의할 것은 body와 다르게 key name으로
headers
를 명시해주어야 하며, value로 필요 객체를 넣어줘야 한다. (headers와 다르게 body의 경우 key name을body
로 명시할 필요가 없으며, 바로 전달하고픈 객체를 넣어주면 된다)
우리가 클론코딩한 사이트는 P2P투자 사이트기에, 유저 정보에 "계좌정보"가 필수적으로 요구되었다.
허나 카카오 로그인 시, 해당 정보를 자동적으로 불러올 수 없기에 카카오 로그인의 경우 로그인 시도 시 바로 회원가입 페이지로 이동시켜, "계좌정보"를 입력시키도록 구현하였다.
보다 회원가입 방법을 편리하게 만들기 위해서, 카카오 로그인 시 승인한 정보들은 (이메일, 이름) 회원가입 페이지로 redirection 시 자동으로 입력되도록 하였다.
이를 구현하기 위해서,
history.push()
에 함께 넘겨서 회원가입 페이지에서 자동으로 입력되도록 구현하였다.const handleKakaoLogin = e => {
const { Kakao } = window;
Kakao.Auth.login({
success: response => {
data
.kakaoLogin(response)
.then(res => {
setKakaoTOKEN(res.access_token);
alert('회원가입 페이지로 이동합니다.');
history.push({
pathname: '/signUp',
state: { res },
});
});
};
});
};
res
라는 객체에는 BE에서 카카오 인증 이후 전달해주는 고객 정보가 들어가있다
{
"res" : {
"user_name": "김코드",
"user_email": "kimcode@gmail.com"
}
}
이 객체를 페이지 이동 시 전달하고자 하는 경우, history.push()
내에 state를 활용하면 된다.
history.push({
pathname: '/signUp',
state: { res },
});
import { useHistory, useLocation } from 'react-router';
function SignUp() {
const history = useHistory();
const location = useLocation();
// 카카오 로그인 시 기본적으로 받아오는 요소들
const kakaoMail = location.state.res.user_email ?? null;
const kakaoName = location.state.res.user_name ?? null;
return (
<Form onSubmit={handleSubmit}>
<input
value={kakaoMail}
/>
{errors.name && <Warning>{errors.name}</Warning>}
</Form>
);
history.push
로 전달받은 데이터는 useLocation()
을 사용하여 출력할 수 있다.
위와 같이 useLocation()
을 선언한 후, location.state.객체명
을 변수에 할당하면, 받아온 객체를 자유롭게 사용할 수 있다.
프로젝트 중 상당히 까다로웠던 작업 중 하나가, input에 숫자가 입력될 때에는 ,콤마
가 자동적으로 입력되게 하면서, 동시에 state에 저장되는 값은 숫자로 저장되게 해야했던 것이다.
이는 아래와 같은 방법으로 해결되었다.
onChange
이벤트를 활용하여 입력되는 값을 state에 우선 저장한다.value
를 사용하여 출력 시 toLocaleString()
이 적용되도록 한다.toLocaleString()
을 통해 문자열로 값이 변경된 것을 onChange
함수가 다시 잡아내여, 이를 숫자로 바꿔주는 작업을 한다.-> 즉 onChange
는 value
가 숫자인지 문자인지 확인한 후(if사용) 각각에 대응하여 기대되는 값으로 변환해주어야 한다.
구현 결과
const handleAmount = e => {
const value = e.target.value;
if (typeof value === 'string') {
if (value.includes(',')) {
setAmount(+value.replaceAll(',', '')); // 문자로 들어온 경우 숫자로 바꿔준 후 state에 저장
} else {
setAmount(+value); // 숫자는 숫자인 채로 state에 저장
}
}
return(
<Input
value={amount.toLocaleString()}
//보여지는 값은 항상 콤마가 입력되어 있어야 하기에 state값에 toLocaleString()이 적용되도록 함
onChange={handleAmount} //값이 변화함을 인지하여 state에 알맞게 처리되도록 함
maxLength="7"
/>
);