회원가입 페이지를 만들어보자. 회원가입 페이지에는 아이디, 닉네임, 비밀번호, 비밀번호 확인 등의 Input 이 필요하고, 해당 값에 대한 초기값과 onChange이벤트를 각각 useState, useCallback을 이용하여 구현해줘야 한다.

그런데 이미 구현해놓은 LoginForm에서도 그렇고 Form 내부의 Input값이 많아질 수록 중복되는 형태의 useState, useCallback(onChange) 이벤트가 많아지므로 이러한 것을 커스텀 훅을 이용해 구현해보자.

// hooks/useInput.js
import { useState, useCallback } from "react";

export default (initialValue = null) => {
  const [value, setValue] = useState(initialValue);
  const handler = useCallback((e) => setValue(e.target.value), []);
  return [value, handler];
};

위처럼 만든 커스텀 훅을 LoginForm 컴포넌트에 적용해보자.

import React, { useCallback, useState } from "react";
import useInput from "../hooks/useInput";

const LoginForm = ({ setIsLoggedIn }) => {
	// 커스텀 훅 useInput 적용
  const [id, onChangeId] = useInput("");
  const [password, onChangePassword] = useInput("");

  return (
    <FormWrapper onFinish={onSubmitForm}>
      <div>
        <label htmlFor="user-id">아이디</label>
        <br />
        <Input name="user-id" value={id} onChange={onChangeId} required />
      </div>
      <div>
        <label htmlFor="user-password">비밀번호</label>
        <br />
        <Input name="user-password" type="password" value={password} onChange={onChangePassword} required />
      </div>
    </FormWrapper>
  );
};

export default LoginForm;

같은 방법으로 signup.js 내 회원가입 페이지를 구현한다.

import { useCallback, useMemo, useState } from "react";
import AppLayout from "../components/AppLayout";
import Head from "next/head";
import { Form, Input, Checkbox, Button } from "antd";
import useInput from "../hooks/useInput";
import styled from "styled-components";

const ErrorMessage = styled.div`
  color: red;
`;

const Signup = () => {
  const [id, onChangeId] = useInput("");
  const [nickname, onChangeNickname] = useInput("");
  const [password, onChangePassword] = useInput("");

// passwordCheck는 중복 체크도 해줘야하므로 별도 처리해준다.
  const [passwordCheck, setPasswordCheck] = useState("");
  const [passwordError, setPasswordError] = useState(false);

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

	// 이용약관 체크도 이벤트에 다른 점이 있으므로 별도로 훅 생성
  const [term, setTerm] = useState("");
  const [termError, setTermError] = useState(false);
  const onChangeTerm = useCallback((e) => {
    setTerm(e.target.checked);
    setTermError(false);
  }, []);

  const buttonStyle = useMemo(() => ({ marginTop: 10 }), []);

  const onSubmit = useCallback(() => {
    if (password !== passwordCheck) {
      return setPasswordError(true);
    }
    if (!term) {
      return setTermError(true);
    }
  }, [password, passwordCheck, term]);

  return (
    <AppLayout>
      <Head>
        <title>회원가입 | NodeBird</title>
      </Head>
      <Form onFinish={onSubmit}>
        <div>
          <label htmlFor="user-id">아이디</label>
          <br />
          <Input name="user-id" value={id} required onChange={onChangeId} />
        </div>
        <div>
          <label htmlFor="user-nick">닉네임</label>
          <br />
          <Input name="user-nick" value={nickname} required onChange={onChangeNickname} />
        </div>
        <div>
          <label htmlFor="user-password">비밀번호</label>
          <br />
          <Input name="user-password" type="password" value={password} required onChange={onChangePassword} />
        </div>
        <div>
          <label htmlFor="user-password-check">비밀번호체크</label>
          <br />
          <Input
            name="user-password-check"
            type="password"
            value={passwordCheck}
            required
            onChange={onChangePasswordCheck}
          />
          {passwordError && <ErrorMessage>비밀번호가 일치하지 않습니다.</ErrorMessage>}
        </div>
        <div>
          <Checkbox name="user-term" checked={term} onChange={onChangeTerm}>
            비키 말을 잘 들을 것을 동의합니다.
          </Checkbox>
          {termError && <ErrorMessage>약관에 동의해야 합니다.</ErrorMessage>}
        </div>
        <div style={buttonStyle}>
          <Button type="primary" htmlType="submit">
            가입하기
          </Button>
        </div>
      </Form>
    </AppLayout>
  );
};

export default Signup;