[Sleact] 슬랙 클론 코딩 - 회원 가입 페이지 만들기 & 커스텀 훅 만들기

인프런 조현영님의 슬랙 클론 코딩입니다.

회원 가입 페이지

로그인을 하려면 회원 가입을 해야하기 때문에 회원가입 페이지를 먼저 만들도록 한다. styles.tsx 파일을 분리하여 작성하였다. 회원 가입에 필요한 이메일, 닉네임, 비밀번호, 비밀번호 확인을 상태로 관리하였고 오류 또한 상태로 관리하였다.

pages/SignUp/index.tsx

import React, { useCallback, useState } from 'react';
import { Form, Error, Label, Input, LinkContainer, Button, Header } from './styles';

const SignUp = () => {
  const [email, setEmail] = useState('');
  const [nickname, setNickname] = useState('');
  const [password, setPassword] = useState('');
  const [passwordCheck, setPasswordcheck] = useState('');
  const [mismatchError, setMismatchError] = useState(false);
  const onChangeEmail = useCallback((e) => {
    setEmail(e.target.value);
  }, []);

  const onChangeNickname = useCallback((e) => {
    setNickname(e.target.value);
  }, []);

  const onChangePassword = useCallback(
    (e) => {
      setPassword(e.target.value);
      setMismatchError(e.target.value !== passwordCheck);
    },
    [passwordCheck],
  );

  const onChangePasswordCheck = useCallback(
    (e) => {
      setPasswordcheck(e.target.value);
      setMismatchError(e.target.value !== password);
    },
    [password],
  );

  const onSubmit = useCallback(
    (e) => {
      e.preventDefault();
      if (!mismatchError && nickname) {
        console.log('서버로 회원가입하기');
      }
    },
    [email, nickname, password, passwordCheck, mismatchError],
  );
  return (
    <div id="container">
      <Header>Sleact</Header>
      <Form onSubmit={onSubmit}>
        <Label id="email-label">
          <span>이메일 주소</span>
          <div>
            <Input type="email" id="email" name="email" value={email} onChange={onChangeEmail} />
          </div>
        </Label>
        <Label id="nickname-label">
          <span>닉네임</span>
          <div>
            <Input type="text" id="nickname" name="nickname" value={nickname} onChange={onChangeNickname} />
          </div>
        </Label>
        <Label id="password-label">
          <span>비밀번호</span>
          <div>
            <Input type="password" id="password" name="password" value={password} onChange={onChangePassword} />
          </div>
        </Label>
        <Label id="password-check-label">
          <span>비밀번호 확인</span>
          <div>
            <Input
              type="password"
              id="password-check"
              name="password-check"
              value={passwordCheck}
              onChange={onChangePasswordCheck}
            />
          </div>
          {mismatchError && <Error>비밀번호가 일치하지 않습니다.</Error>}
          {!nickname && <Error>닉네임을 입력해주세요.</Error>}
          {/* {signUpError && <Error>{signUpError}</Error>}
          {signUpSuccess && <Success>회원가입되었습니다! 로그인해주세요.</Success>} */}
        </Label>
        <Button type="submit">회원가입</Button>
      </Form>
      <LinkContainer>
        이미 회원이신가요?&nbsp;
        {/* <Link to="/login">로그인 하러가기</Link> */}
      </LinkContainer>
    </div>
  );
};

export default SignUp;

useCallback 훅을 통해 함수가 불필요하게 다시 만들어지는 것을 막았다. 이때 dependencies 는 함수 내부에서 사용하는 인자가 아닌 외부의 인자로 사용해주어야 한다.

코드를 다시 보면 형태가 비슷비슷한 코드들을 많이 볼 수 있다. 그렇다면 코드를 좀 줄일 수 있지 않을까? 라는 생각을 해야한다.

커스텀 훅 만들기

hooks/useInput.ts

import { ChangeEvent, Dispatch, SetStateAction, useCallback, useState } from 'react';

type ReturnTypes<T = any> = [T, (e: ChangeEvent<HTMLInputElement>) => void, Dispatch<SetStateAction<T>>];

const useInput = <T>(initialData: T): ReturnTypes<T> => {
  const [value, setValue] = useState(initialData);
  const handler = useCallback((e) => {
    setValue((e.target.value as unknown) as T);
  }, []);
  return [value, handler, setValue];
};

export default useInput;

타입스크립트로 작성하면 가독성이 안 좋은 것은 사실이다. 어쨌든 위와 같이 커스텀 훅을 작성하고 아래와 같이 적용한다.
pages/SignUp/index.tsx

const SignUp = () => {
  const [email, onChangeEmail] = useInput('');
  const [nickname, onChangeNickname] = useInput('');
  const [password, , setPassword] = useInput('');
  const [passwordCheck, , setPasswordcheck] = useInput('');
  const [mismatchError, setMismatchError] = useState(false);
  ...
}

필요하지 않거나 안 쓰는 요소는 비워두면 된다.

axios로 요청 보내기와 CORS, proxy

pages/SignUp/index.tsx

const onSubmit = useCallback(
    (e) => {
      e.preventDefault();
      if (!mismatchError && nickname) {
        console.log('서버로 회원가입하기');
        axios
          .post('http://localhost:3095/api/users', {
            email,
            nickname,
            password,
          })
          .then((response) => {
            console.log(response);
          })
          .catch((error) => {
            console.log(error.response);
          })
          .finally(() => {});
      }
    },
    [email, nickname, password, passwordCheck, mismatchError],
  );

위처럼 axios 를 사용하였다.

회원가입을 실제로 진행해보면 성공적으로 post 된 것을 확인할 수 있다. 이 때 요청을 두 번 보내는데 OPTIONS 라는 요청을 하고 있다. 이것이 바로 그 유명한 CORS 에 대한 요청이다.

프론트와 백엔드 서버의 주소(여기서는 port)가 다르기 때문에 생기는 문제인데 주소가 다른데 현재 요청이 정상적으로 이뤄지고 있다. 그 이유는 백엔드에 어떠한 장치를 해놨기 때문인데

바로 이거 덕분이다. 따라서 CORS 관련 문제가 발생한다면 백엔드 개발자에게 보여주며 요청하는 방법이 있고 프론트 개발자가 직접 해결하는 방법이 있다.

webpack.config.js

웹팩에서 위와 같이 작성한다. 그 뜻은 /api/ 로 보내는 요청은 주소를 target 으로 바꿔 보내겠다라는 뜻이다. 하지만 이 방법은 프론트와 백 둘다 localhost에서 돌아가는 환경일 때만 유효하다.

비동기 요청에서의 setState

pages/SignUp/index.tsx

const onSubmit = useCallback(
    (e) => {
      e.preventDefault();
      if (!mismatchError && nickname) {
        console.log('서버로 회원가입하기');
        setSignUpError('');
        setSignUpSuccess(false);
        axios
          .post('/api/users', {
            email,
            nickname,
            password,
          })
          .then((response) => {
            console.log(response);
            setSignUpSuccess(true);
          })
          .catch((error) => {
            console.log(error.response);
            setSignUpError(error.response.data);
          })
          .finally(() => {});
      }
    },
    [email, nickname, password, passwordCheck, mismatchError],
  );

위와 같이 비동기 요청에서 then, catch, finally 에서 setState 하는 부분이 있을 수 있다. 이 부분은 보통 비동기 요청을 하기 전에 초기화를 해주는 것이 좋다. 그렇지 않으면 요청을 연달아 날릴 때 전의 요청에서 남아 있던 결과가 남아있는 경우가 있기 때문이다.

좋은 웹페이지 즐겨찾기