글쓰는쿼카의 PM 여정
[FP] 마이페이지의 닉네임 변경하기 (2024. 7. 29.) 본문
#스파르타코딩클럽 #내일배움캠프(프론트엔드_React)
학습주제 : Next.js - route handler, React useState
학습내용 : 닉네임에 대한 유효성 검사, 닉네임 중복 확인 기능
학습일 : 2024. 7. 29.
배운 내용 요약
기능 기획 단계
1. 무엇을 만들까?
마이페이지에서 닉네임을 변경하는 기능을 만들어보고 싶다.
유저 입장에서 귀찮은 과정은 빠르게 지나가면 좋기 때문에 타자를 치면서 오류 메시지를 바로 볼 수 있게 하고 싶다.
아래 고려해야 할 큰 카데고리 중 2가지만 집중적으로 다루고자 한다.
( 👇🏻 고려해야 할 큰 카테고리)
- UI 뼈대
- 이벤트 함수
- 이벤트 함수를 통한 화면 변화
- 유효성 검사의 기준 (⭐여기!)
- 데이터베이스 서버 통신이 필요한 부분 (⭐여기!)
2. 유효성 검사 기준 정하기
유효한 닉네임인지 아닌지 판가름 하기 위한 기준이 필요하다.
아래 내용은 우리가 흔히 아는 유효성 검사 목록 중 적용해본 것들을 작성해보았다.
4가지 중 하나('중복 확인')는 데이터베이스 서버 통신이 필요하기 때문에 따로 다루도록 하겠다.
( 👇🏻 한번쯤 경험해봤을 흔한 유효성 검사 목록)
- 빈칸 - "빈 곳을 채워주세요."
- 문자열 길이 - "3자 이상, 12자 이하만 가능합니다."
- 특수문자 - "특수문자는 사용할 수 없습니다."
- 중복 확인 - "동일한 닉네임이 있습니다." (⭐DB 서버 통신!)
3. 어떻게 만들까?
1) React 활용
타자를 치면서 오류 메시지를 바로 볼 수 있게 하려면 닉네임을 입력하는 인풋 태그의 onClick 콜백함수로 useState 또는 useEffect를 활용하면 좋다. 아래는 코드 작성 플로우를 키워드 위주로 작성한 내용이다.
- 입력되는 e.target.value를 저장하는 기능
- 저장된 state를 조건대로 검사하는 함수 실행 --- 다른 페이지(예: 회원가입)에서도 사용할 수 있도록 컴포넌트화!
- 모르는 건 검색: 이메일 또는 특수문자의 정규식 표현
- 상태값을 기반으로 삼항연산자를 활용하여 오류 메시지 보여주기
( 👇🏻전체 코드 - 파일 2개)
// src > app > (main) > mypage > edit > _components > InfoContainer.tsx
export const CheckNicknameValidity = ({
nickname,
setNicknameError,
setIsNicknameValid
}: {
nickname: string;
setNicknameError: React.Dispatch<React.SetStateAction<string>>;
setIsNicknameValid: React.Dispatch<React.SetStateAction<boolean>>;
}) => {
if (!nickname) {
setNicknameError("빈칸을 채워주세요.");
setIsNicknameValid(false);
return;
}
if (nickname.length < 3 || nickname.length > 12) {
setNicknameError("닉네임은 3자 이상 12자 이하여야 합니다.");
setIsNicknameValid(false);
return;
}
if (/[^a-zA-Z0-9]/.test(nickname)) {
setNicknameError("닉네임에는 특수문자를 사용할 수 없습니다.");
setIsNicknameValid(false);
return;
}
setNicknameError("");
setIsNicknameValid(true);
};
// src > app > (main) > mypage > edit > _components > checkValidity.tsx
export const CheckNicknameValidity = ({
nickname,
setNicknameError,
setIsNicknameValid
}: {
nickname: string;
setNicknameError: React.Dispatch<React.SetStateAction<string>>;
setIsNicknameValid: React.Dispatch<React.SetStateAction<boolean>>;
}) => {
if (!nickname) {
setNicknameError("빈칸을 채워주세요.");
setIsNicknameValid(false);
return;
}
if (nickname.length < 3 || nickname.length > 12) {
setNicknameError("닉네임은 3자 이상 12자 이하여야 합니다.");
setIsNicknameValid(false);
return;
}
if (/[^a-zA-Z0-9]/.test(nickname)) {
setNicknameError("닉네임에는 특수문자를 사용할 수 없습니다.");
setIsNicknameValid(false);
return;
}
setNicknameError("");
setIsNicknameValid(true);
};
2) 라우트 핸들러 활용 - router.ts
닉네임 중복확인을 하기 위해서는 데이터베이스의 유저 정보를 조회해야 한다.
(라우트 핸들러 사용법을 이해하기까지 정말 오래 걸렸는데, 드디어 감🍊을 잡았다)
데이터베이스를 활용하려면 서버와 통신하는 방법을 알아야 하는데, 크게 4가지를 알아야 한다.
- request, response --> 요청과 응답
- request의 method, headers, body --> 요청의 내용
- response 상태 200대, 400대, 500대 --> 200대: 요청&응답 정상, 400대: 클라이언트 오류, 500대: 서버 오류
- response 상태에 따라 직접 error 만들기
( 👇🏻서버 통신 method별 예시)
- GET : 조회해서 데이터를 가져온다 (특징: request의 body 사용 안 함)
-> 예시: 게시글 목록 가져오기, userId가 일치하는 유저의 회원 정보 가져오기 - POST : 최초의 데이터를 만든다 (특징: requestd의 body 사용함)
-> 예시: 게시글 작성하기, 변경할 닉네임의 중복 확인하기, 로그인⭐⭐⭐ - PATCH 또는 PUT : 기존에 있는 데이터를 수정한다
-> 예시: 게시글 수정하기, 닉네임 변경하기 - DELETE : 데이터를 삭제한다
-> 예시: 게시글 삭제하기
( 👇🏻전체 코드 - 파일 3개, 마지막 route에 주목!)
// src > app > (main) > mypage > edit > _components > UserEditNickname.tsx
"use client";
import { updateNickname } from "@/apis/auth/mypage/mypageInfo";
import { CheckNicknameValidity } from "@/app/(main)/mypage/edit/_components/checkValidity";
import { ChangeEvent, FormEvent, MouseEventHandler, useState } from "react";
function UserEditNickname() {
const [nickname, setNickname] = useState<string>("");
const [nicknameError, setNicknameError] = useState<string>("");
const [isNicknameValid, setIsNicknameValid] = useState<boolean>(false);
// 유효성 검사 통과할 경우: isNicknameValid === true
// 유효성 검사 통과 못 할 경우: isNicknameValid === false
const [nicknameDupError, setNicknameDupError] = useState<string>("");
const [isNicknameAllPassed, setIsNicknameAllPassed] = useState<boolean>(false);
// 중복확인 통과할 경우: isNicknameAllPassed === true
// 중복확인 통과 못 할 경우: isNicknameAllPassed === false
const handleNicknameChange = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
// 유효성 검사
// CheckNicknameValidity({ nickname, setNicknameError, setIsNicknameValid });
// 유효성 검사 통화 못 할 경우 중복확인 실시 안 함
if (!isNicknameValid) {
setNicknameDupError("");
setIsNicknameAllPassed(false);
}
// 3. 중복확인
if (isNicknameValid) {
await updateNickname(nickname, setNicknameDupError, setIsNicknameAllPassed);
}
};
return (
<div className="AuthInputDiv">
<label>닉네임 변경</label>
<form className="AuthInputForm" onSubmit={handleNicknameChange}>
<input
type="text"
value={nickname}
placeholder="변경할 닉네임을 입력해주세요"
className="AuthInputShort"
onChange={(e) => {
setNickname(e.target.value);
CheckNicknameValidity({ nickname: e.target.value, setNicknameError, setIsNicknameValid });
if (isNicknameAllPassed) {
setNicknameDupError("");
setIsNicknameAllPassed(false);
}
}}
/>
<button className="AuthDupButton" type="submit">
중복확인
</button>
</form>
{isNicknameValid ? "" : <p className="AuthStateInfo">{nicknameError}</p>}
{isNicknameAllPassed ? (
<p className="AuthStateInfoGreen">{nicknameDupError}</p>
) : (
<p className="AuthStateInfo">{nicknameDupError}</p>
)}
</div>
);
}
export default UserEditNickname;
// src > apis > auth > mypage > mypageInfo.ts
import { Dispatch, SetStateAction } from "react";
export const updateNickname = async (
nickname: string,
setNicknameDupError: Dispatch<SetStateAction<string>>,
setIsNicknameAllPassed: Dispatch<SetStateAction<boolean>>
) => {
const res = await fetch("/api/auth/mypage/test", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(nickname)
});
if (res.status === 200) {
setNicknameDupError("사용 가능한 닉네임 입니다");
setIsNicknameAllPassed(true);
}
if (res.status === 409) {
setNicknameDupError("동일한 닉네임이 있습니다.");
setIsNicknameAllPassed(false);
}
if (res.status === 500) {
setNicknameDupError("알 수 없는 에러가 발생했습니다.");
setIsNicknameAllPassed(false);
}
};
// src > app > api > auth > mypage > test > route.ts
import { createClient } from "@/utils/supabase/server";
import { NextRequest, NextResponse } from "next/server";
export const POST = async (req: NextRequest) => {
const supabase = createClient();
const nickname = await req.json();
const { data } = await supabase.from("users").select("*").eq("nickname", nickname).single();
if (!data) {
return NextResponse.json("사용 가능한 닉네임입니다."); (⭐ 여기!)
}
if (data) {
return NextResponse.json({ error: "동일한 닉네임이 있습니다." }, { status: 409 });
} (⭐ 여기!)
return NextResponse.json({ error: "알 수 없는 에러가 발생했습니다." }, { status: 500 });
}; (⭐ 여기!)
'개발 > Next.js' 카테고리의 다른 글
[FP] Context API 로 유저 상태 관리하기 (2024. 7. 25.) (0) | 2024.07.25 |
---|---|
🐤[베이직] Next.js - 간단하게 훑기 (2024. 6. 28.) (0) | 2024.06.28 |