React

[React] 간단한 회원가입 로그인 구현하기

solfa 2024. 1. 20. 03:33

소셜같은 복잡한 로그인 말고 아이디와 비밀번호 두 가지만 가지고 회원가입 / 로그인을 구현해봤다.

일단 api 명세는 다음과 같다.

- 둘 다 정보를 서버로 보내는 것이니까 POST를 사용하면 된다.

- 회원가입의 requestbody에서 language, city, gu는 프로젝트에서 추가로 사용한 회원의 정보이다 이런 식으로 정보를 추가로 전달하는 것이 가능하다.

- 보통 responsebody는 따로 받아오지 않는다. 다만 로그인 페이지에서 로그인 한 것을 기록하는 느낌으로 토큰을 저장하는 정도만 하면 된다.

- 아이디로 이메일을 고른 이유 : 실제로 있는 이메일인지 판별하거나 소셜 로그인에 필요해서 이메일을 고른 것이 아니다! 해커톤 프로젝트였기 때문에 아이디 중복 검사나 아이디 길이 제한과 같은 예외처리를 최소화 하기 위해 그냥 최대한 중복이 안생기도록 이메일을 아이디로 골랐다. 간단한 구현이기 때문에 어떠한 형태든 상관 없다.

 

1. 회원가입

회원가입을 구현하는 함수 handleSignUp

const handleSignUp = async () => {
    // 폼이 유효한지 확인
        try {
            // 서버에 회원가입 정보를 전송하는 HTTP POST 요청
            const response = await fetch('서버에서 받아온 api 넣으세요', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    email,       // 사용자가 입력한 이메일
                    password,    // 사용자가 입력한 비밀번호
                    language,    // 사용자가 선택한 언어
                    city,        // 사용자가 선택한 도시
                    gu,          // 사용자가 선택한 구/군
                }),
            });

            // HTTP 응답이 성공인 경우
            if (response.ok) {
                console.log("성공");
            } else {
                // 서버에서 에러 응답을 받은 경우, 에러 메시지 처리
                const data = await response.json();
                setErrorMessage(data.message || '등록 실패');
                //보통 이런 식으로 에러 처리를 합니다 저는
            }
        } catch (error) {
            // 서버 통신 중 오류 발생 시, 에러 메시지 처리
                console.log("에러");
        }
    }
};

- 대부분 비슷한 형태여서 body만 매번 api 명세에 따라 바꾸거나 하면 된다!

- method가 POST가 아닌 GET이나 PUT은 로직이 많이 다른 걸로 기억! 대부분의 POST가 이 형태라는 뜻이다!

 

최종 코드와 결과물

import { useState, useEffect} from "react"
import { Lttext, LText, StartButtonStyle, Ttitle, Wrapper, InputStyled, SelectStyled } from "../../styles/styles";
import { useNavigate } from "react-router-dom";
import {sejongDistricts, seoulDistricts, gyeonggiDistricts, gangwonDistricts, knDistricts, kbDistricts, icDistricts, busanDistricts, daejeonDistricts, dagueDistricts, ulsanDistricts, jejuDistricts, jnDistricts, jbDistricts, cnDistricts, cbDistricts, gwangjuDistricts} from '@/pages/register/data';
import {languageOptions, regionOptions} from '@/pages/register/data';

export default function RegisterPage() {

    const navigate = useNavigate();
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [language, setLanguage] = useState('ko');
    const [city, setCity] = useState('seoul');
    const [gu, setGu] = useState('');
    const [errorMessage, setErrorMessage] = useState('');
    const [availableDistricts, setAvailableDistricts] = useState([]);

    useEffect(() => {
        // ... (unchanged)
    }, [city]);

    const handleEmailChange = (e) => {
        setEmail(e.target.value);
    };

    const handlePasswordChange = (e) => {
        setPassword(e.target.value);
    };

    const handleLanguageChange = (e) => {
        setLanguage(e.target.value);
    };

    const handleCityChange = (e) => {
        setCity(e.target.value);
    };

    const handleGuChange = (e) => {
        setGu(e.target.value);
    };

    const isFormValid = email !== '' && password !== '' && language !== '' && city !== '' && gu !== '';

    const handleSignUp = async () => {
        if (isFormValid) {
            try {
                const response = await fetch('http://3.39.62.158:8080/users/join', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        email,
                        password,
                        language,
                        city,
                        gu,
                    }),
                });

                if (response.ok) {
                    navigate("/");
                    console.log("성공");
                } else {
                    const data = await response.json();
                    setErrorMessage(data.message || '등록 실패');
                }
            } catch (error) {
                setErrorMessage('등록 중 오류가 발생했습니다');
            }
        } else {
            setErrorMessage("모든 정보를 기입해주세요.");
            setTimeout(() => {
                setErrorMessage('');
            }, 2000);
        }
    };

    return (
        <Wrapper>
            {/* ... (unchanged) */}
            <StartButtonStyle style={{marginTop:'100px'}} onClick={handleSignUp}>
                가입(SignUp)
            </StartButtonStyle>
        </Wrapper>
    );
}

- handleSignUp 함수를 만들었다면 써먹어야 하겠죠?? onChange를 활용해 가입 버튼을 누르는 순간에 함수를 실행시켜서 기입한 정보를 서버에 보내는 형식으로 return을 적어준다.

- useState를 통해 값을 입력받는 느낌이라고 알면 된다.

- useState를 콜백하는 함수를 각자 body의 한 항목에 부여하는 느낌으로! 저렇게 하나하나 만들어주고 현재 코드상에는 없는데 이 역시 return에서 입력받거나 (Input) 선택하거나 (Select) 할 때 onChange를 활용해 정보 상태를 계속 업데이트 해주면서 서버에 보내준다.

// InputStyled 랑 SelectStyled는 제가 만든 스타일 컴포넌트입니다! 위 코드에서 <ButtonStyle어쩌구 그거랑 같은 의미에요>
// 그냥 <Input/>이랑 <Select/>랑 같아요
<InputStyled type="password" value={password} onChange={handlePasswordChange} placeholder="password"></InputStyled>

<SelectStyled value={language} onChange={handleLanguageChange}>

이런식으로!

2. 로그인

로그인을 구현하는 함수 handleSignUp

const handleSignIn = async () => {
        if (isFormValid) {
            try {
                const response = await fetch('http://3.39.62.158:8080/users/login', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        email: email,
                        password: password,
                    }),
                });

                if (response.ok) {
                    const data = await response.json();
                    // 로그인 성공
                    console.log("로그인 성공");
                    setErrorMessage(data.message || '로그인 성공');
                } 
                else if(response.status === 400){
                    const data = await response.json();
                    setErrorMessage(data.message || '로그인 실패');
                }
                else {
                    // 로그인 실패
                    const data = await response.json();
                    setErrorMessage(data.message || '로그인 실패');
                }
            } catch (error) {
                //console.error('로그인 중 오류 발생:', error);
                setErrorMessage('로그인 중 오류가 발생했습니다');
            }
        } else {
            setErrorMessage("이메일과 비밀번호를 모두 입력하세요.");
        }
    };

- setErrorMessage는 나중에 전체 코드에 나오는 내가 만든 에러 상태를 전달하기 위한 useState이다. 그냥 저 부분에 에러 처리나 하고 싶은 행동을 집어 넣으면 된다.

- isFormValid도 내가 만든 예외처리 중 하나이다. 정보를 다 기입해야 가입이나 로그인이 되도록 처음부터 처리하면 좋다.

- 회원가입과 body만 다르다.

 

최종 코드 및 결과물

import { useState } from "react";
import {  InputStyled, LText, Left, Lttext, StartButtonStyle, Ttitle, Wrapper } from "../../styles/styles";
import { useNavigate } from "react-router-dom";

export default function LoginPage() {

    const navigate = useNavigate();
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [errorMessage, setErrorMessage] = useState('');

    const isFormValid = email !== '' && password !== '';

    const handleSignIn = async () => {
        if (isFormValid) {
            try {
                const response = await fetch('http://3.39.62.158:8080/users/login', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        email: email,
                        password: password,
                    }),
                });

                if (response.ok) {
                    const data = await response.json();
                    // 로그인 성공
                    console.log("로그인 성공");
                    setErrorMessage(data.message || '로그인 성공');
                    setTimeout(() => {
                        navigate("/");
                    }, 500)
                } 
                else if(response.status === 400){
                    const data = await response.json();
                    setErrorMessage(data.message || '로그인 실패');
                }
                else {
                    // 로그인 실패
                    const data = await response.json();
                    setErrorMessage(data.message || '로그인 실패');
                }
            } catch (error) {
                //console.error('로그인 중 오류 발생:', error);
                setErrorMessage('로그인 중 오류가 발생했습니다');
            }
        } else {
            setErrorMessage("이메일과 비밀번호를 모두 입력하세요.");
            setTimeout(() => {
                setErrorMessage('');
            }, 2000);
        }
    };

    const handleEmailChange = (e) => {
        const { value } = e.target;
        console.log(value);
        setEmail(value);
    };

    const handlePasswordChange = (e) => {
        const { value } = e.target;
        console.log(value);
        setPassword(value);    
    };


    return(
        <Wrapper>
            <Ttitle>
                로그인
            </Ttitle>
            <LText>
                로그인시 00일동안 데이터가 저장됩니다.
            </LText>
            <Lttext style={{marginTop:'60px'}}>
                이메일(email)
            </Lttext>
                <InputStyled type="email" value={email} onChange={handleEmailChange} placeholder="email"></InputStyled>
            <Lttext style={{marginTop:'30px'}}>
                비밀번호(password)
            </Lttext>
                <InputStyled type="password" value={password} onChange={handlePasswordChange} placeholder="password"></InputStyled>
                <div style={{ position: "absolute", top:'500px', opacity: errorMessage ? 1 : 0, height: errorMessage ? 'auto' : 0, overflow: 'hidden', transition: "opacity 0.5s, height 0.5s" }}>
                    <p style={{ color: "red" }}>{errorMessage}</p>
                </div>   
            <StartButtonStyle  style={{marginTop:'200px'}} onClick={handleSignIn}>
                로그인(Signin)
            </StartButtonStyle>
        </Wrapper>
    )
};

 

다음엔 소셜 로그인이랑 PUT 구현하고 싶다!

GET으로 많은 데이터 가져와서 프론트에서 처리하는 것도 해보고 싶다.

왜 파싱은 전부 백에서 하는가... 원래 그런가?

암튼 갈 길이 멀다... 홧팅하쟈

728x90