[React 프로젝트] 숫자야구 프로젝트 - 회원가입

Hyuk·2023년 1월 15일
0

JoinMembership 컴포넌트는 말 그대로 사용자의 회원가입을 위한 페이지이고 코드의 대부분은 로그인 기능을 도맡은 Home 컴포넌트와 유사하다.

전체 코드

// src/view/JoinMemberShip/index.js
import axios from 'axios'
import React, { useCallback, useState, useMemo } from 'react'
import { useHistory } from 'react-router-dom'
import ValidationForm from '../../components/joinValidationForm'
import './index.css'

const JoinMembership = () => {
    const history = useHistory()
    const [profile, setProfile] = useState({
        id: '',
        password: '',
        repassword: '',
        nickname: ''
    })
    const [code, setCode] = useState(0)

    const validateRepassword = useMemo(() => {
        return profile.password && profile.password.length > 0
            && profile.password === profile.repassword ? 
            { 
                result: true, 
                errorMessage: '패스워드 확인 완료.'
            } : 
            undefined
    }, [profile.password, profile.repassword])

    const onJoinMembershipClick = useCallback(() => {
        axios.post('http://localhost:65100/join', profile)
        .then(() => {
            alert("회원가입이 완료되었습니다.")
            history.push('/')
        })
        .catch((error) => {
            const { code } = error.response.data
            if (code === 1){
                setCode(code)
            }
            if (code === 2){
                setCode(code)
            }
        })
    }, [profile])

    return (
        <div className='container' style={{ background: 'whitesmoke' }}>
            <div className='join-membership-form' style={{ background: 'white' }}>
                <h1 className='header'>
                    회원가입
                </h1>
                <div className='input-form'>
                    <div className='id-form'>
                        <label htmlFor='join-input-id'>
                            아이디
                        </label>
                        <ValidationForm 
                            id='join-input-id'
                            className='input border-solid border border-gray-500 font-sans ... rounded-sm ...'
                            placeholder='아이디 입력'
                            maxLength='15'
                            value={profile.id}
                            error={code === 1 ? { result: false, errorMessage: '아이디가 중복되었습니다.'} : undefined}
                            validations={
                                [
                                    (value) => {
                                        return {
                                            result: value && value.length >= 5,
                                            errorMessage: '아이디를 5자이상 입력해주세요.'
                                        }
                                    }
                                ]
                            }
                            onChange={(event) => {setProfile({...profile, id: event.target.value})}}
                        />
                    </div>
                    <div className='pw-form'>
                        <label htmlFor='join-input-pw'>
                            비밀번호
                        </label>
                        <ValidationForm 
                            id='join-input-pw'
                            className='input border-solid border border-gray-500 font-sans ... rounded-sm ...'
                            placeholder='비밀번호 입력(6~20자)'
                            type='password'
                            maxLength='20'
                            value={profile.password}
                            validations={
                                [
                                    (value) => {
                                        return {
                                            result: value && value.length >= 6,
                                            errorMessage: '패스워드를 6자이상 입력해주세요.'
                                        }
                                    }
                                ]
                            }
                            onChange={(event) => {setProfile({...profile, password: event.target.value})}}
                        />
                        <ValidationForm 
                            id='repassword'
                            className='input border-solid border border-gray-500 font-sans ... rounded-sm ... mt-2 ...'
                            placeholder='비밀번호 재확인'
                            type='password'
                            maxLength='20'
                            value={profile.repassword}
                            error={validateRepassword}
                            validations={[]}
                            onChange={(event) => {setProfile({...profile, repassword: event.target.value})}}
                        />
                    </div>
                    <div className='nn-form'>
                        <label htmlFor='join-input-nn'>
                            닉네임
                        </label>
                        <ValidationForm
                            id = 'join-input-nn'
                            className='input border-solid border border-gray-500 font-sans ... rounded-sm ...'
                            placeholder='닉네임 입력(2~8자)'
                            maxLength='8'
                            value={profile.nickname}
                            error={code === 2 ? { result: false, errorMessage: '닉네임이 중복되었습니다.'} : undefined}
                            validations={[
                                (value) => ({
                                    result: value && value.length >= 2,
                                    errorMessage: '닉네임을 2자 이상 입력해주세요.',
                                    properMessage: '올바른 닉네임입니다.'
                                })
                            ]}
                            onChange={(event) => {setProfile({...profile, nickname: event.target.value})}}
                        />
                    </div> 
                </div>
                <button className='button' onClick={onJoinMembershipClick}> 가입하기</button>
            </div>
        </div>
    )
}

export default JoinMembership

전체 코드는 위와 같고 이제 기능별로 자세하 다뤄보자.
기능과 연관이 없는 코드(className 등)은 지우겠다.

백엔드와 통신

Home 컴포넌트에서 profile 변수를 전해주면서 axios를 이용해 백엔드와 통신했다. JoinMemberShip 컴포넌트에서도 동일한 방식으로 원하는 데이터를 주고받을 수 있다.

// src/view/JoinMemberShip/index.js
import axios from 'axios'

const JoinMembership = () => {

    const [profile, setProfile] = useState({
        id: '',
        password: '',
        repassword: '',
        nickname: ''
    })
    const [code, setCode] = useState(0)

    const onJoinMembershipClick = useCallback(() => {
        axios.post('http://localhost:65100/join', profile)
        .then(() => {
            alert("회원가입이 완료되었습니다.")
            history.push('/')
        })
        .catch((error) => {
            const { code } = error.response.data
            if (code === 1){
                setCode(code)
            }
            if (code === 2){
                setCode(code)
            }
        })
    }, [profile])
}

백엔드와 통신함으로써 데이터베이스에 중복된 아이디 및 닉네임이 있는지 판단할 수 있다.
데이터베이스에 중복된 값이 있다면 중복된 아이디 혹은 중복된 닉네임을 뜻하는 것이므로 다음과 같이 code의 값에 따라 경고 문구가 쓰여질 것이다.

하지만 입력을 제대로 했는지에 대한 유효성 검사는 백엔드와 통신없이 유효성 검사를 진행하기 때문에 별도의 컴포넌트를 만들어야 한다. 자세한 내용은 다음 챕터에 기록해두겠다.

다음과 같이 onChange 함수로 사용자가 입력한 value값을 추적해 profile 변수에 담는다.

// src/view/JoinMemberShip/index.js
<ValidationForm 
    ...
    onChange={(event) => {setProfile({...profile, id: event.target.value})}}
/>
...

유효성 검사

회원가입 시 형식에 맞는 입력 사항을 적었는지와 중복의 여부를 판단하기 위해 유효성 검사를 진행한다.
로그인 할 때와 마찬가지로 효율성 증대와 확장성이 용이하도록 별도의 컴포넌트를 만들어 진행했다.
다음은 회원가입 유효성 검사를 위한 JoinValidation의 전체 코드이다.

// src/JoinValidation/index.js
import React, { useState, useMemo } from 'react'
import './index.css'

const ValidationForm = ({
    value,
    error,
    validations = [],
    containerStyle = {},
    ...props
    }) => {  
        
    const [errorMessage, setErrorMessage] = useState('')

    const isValid = useMemo(() => {
        if (
            error
            && error.errorMessage !== undefined
            && error.result !== undefined
        ) {
            setErrorMessage(error.errorMessage)
            return error.result
        }

        for (const validation of validations){
            const { result, errorMessage } = validation(value)
            if(!result) {
                setErrorMessage(errorMessage)
                return result
            }
        }

        setErrorMessage('')
        return true
    }, [validations])

    return (
        <div className='id-container' style={containerStyle}>
           <input value={value} {...props}/>
           <span>{
               isValid ? 
               <span className='proper-message'>{errorMessage}</span> :
               <span className='error-message'>{errorMessage}</span> 
           }</span>
        </div> 
    )
}

export default ValidationForm
// src/view/JoinMemberShip/index.js
<div className='nn-form'>
    <label htmlFor='join-input-nn'>
        닉네임
    </label>
    <ValidationForm
        id = 'join-input-nn'
        className='input border-solid border border-gray-500 font-sans ... rounded-sm ...'
        placeholder='닉네임 입력(2~8자)'
        maxLength='8'
        value={profile.nickname}
        error={code === 2 ? { result: false, errorMessage: '닉네임이 중복되었습니다.'} : undefined}
        validations={[
            (value) => ({
                result: value && value.length >= 2,
                errorMessage: '닉네임을 2자 이상 입력해주세요.',
                properMessage: '올바른 닉네임입니다.'
            })
        ]}
        onChange={(event) => {setProfile({...profile, nickname: event.target.value})}}
    />
</div>

로그인할 때에는 아이디가 틀렸는지, 비밀번호가 틀렸는지 불특정하게 나타내야 했다면, 회원가입할 때에는 어느 부분이 틀렸는지 나타내줘야하기때문에 정확한 errorMessage를 반환해 줘야한다.
또한 아이디와 닉네임의 경우 중복 여부까지 나타내줘야 하기 때문에 error라는 값을 기입해줌으로써 validationFormprops로 전달해주었다.

백엔드와 통신 후 불러온 데이터 중 code === 1은 아이디 중복, code === 2는 닉네임 중복을 뜻한다.

회원가입할 때 사용자가 비밀번호를 2번 입력해서 일치여부를 확인한다.
그렇기 때문에 2차 비밀번호 입력칸에는 중복여부에 대한 유효성 검사와 조금 다르게 진행된다. 우선, 다음과 같이 비밀번호와 일치하는지에 대해 판단하는 함수를 만든다.

// src/view/JoinMemberShip/index.js
const validateRepassword = useMemo(() => {
      return profile.password && profile.password.length > 0
          && profile.password === profile.repassword ? 
          { 
              result: true, 
              errorMessage: '패스워드 확인 완료.'
          } : 
          undefined
  }, [profile.password, profile.repassword])

그 후, 다음과 같이 error라는 값으로 props로 전달해 줌으로써 undefined를 기준으로 값을 판단해준다.

validations={[]} 의 값은 비워두었다.

<ValidationForm 
    id='repassword'
    placeholder='비밀번호 재확인'
    type='password'
    maxLength='20'
    value={profile.repassword}
    error={validateRepassword}
    validations={[]}
    onChange={(event) => {setProfile({...profile, repassword: event.target.value})}}
/>
profile
프론트엔드 개발자

0개의 댓글