목차


회원가입 및 로그인 기능을 클라이언트에서 구현한 내용을 정리해보겠습니다.
1. 회원가입 기능 구현
회원가입 기능은 useActionState를 활용하여 서버 요청과 응답을 관리하도록 구현했습니다.
회원가입 요청을 보낼 때 상태를 관리하고, 성공 또는 실패 여부를 사용자에게 보여줄 수 있도록 했습니다.
1-1. 회원가입 컴포넌트
import { useActionState, useEffect, useState } from 'react';
import { Eye, EyeOff, UserRound } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { register } from '@/actions/userActions';
type Props = {
setIsRegister: React.Dispatch<React.SetStateAction<boolean>>;
};
export default function Register({ setIsRegister }: Props) {
const [formData, setFormData] = useState({
email: '',
password: '',
passwordCheck: '',
});
const [state, formAction, isPending] = useActionState(register, {
success: null,
error: null,
});
const [showPassword, setShowPassword] = useState({
password: false,
passwordCheck: false,
});
const toggleShowPassword = (field: 'password' | 'passwordCheck') => {
setShowPassword((prev) => ({ ...prev, [field]: !prev[field] }));
};
const [message, setMessage] = useState<{
text: string | null;
type: 'success' | 'error' | null;
}>({
text: null,
type: null,
});
useEffect(() => {
if (state.success) {
const timer = setTimeout(() => {
setIsRegister(false);
setFormData({ email: '', password: '', passwordCheck: '' });
}, 1000);
return () => clearTimeout(timer);
}
}, [state.success]);
useEffect(() => {
if (state.success || state.error) {
setMessage({
text: state.success || state.error,
type: state.success ? 'success' : 'error',
});
const timer = setTimeout(() => {
setMessage({ text: null, type: null });
state.success = null;
state.error = null;
}, 1000);
return () => clearTimeout(timer);
}
}, [state.success, state.error]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
return (
<div className='form-box register'>
<form action={formAction}>
<h1>Registration</h1>
<div className='input-box'>
<Input
type='email'
name='email'
placeholder='Email Address'
value={formData.email}
onChange={handleChange}
required
/>
<UserRound />
</div>
<div className='input-box'>
<Input
type={showPassword.password ? 'text' : 'password'}
name='password'
placeholder='Password'
value={formData.password}
onChange={handleChange}
required
/>
<button
type='button'
onClick={() => toggleShowPassword('password')}
className='flex items-center'
>
{showPassword.password ? <Eye /> : <EyeOff />}
</button>
</div>
<div className='input-box'>
<Input
type={showPassword.passwordCheck ? 'text' : 'password'}
name='passwordCheck'
placeholder='Password Check'
value={formData.passwordCheck}
onChange={handleChange}
required
/>
<button
type='button'
onClick={() => toggleShowPassword('passwordCheck')}
className='flex items-center'
>
{showPassword.passwordCheck ? <Eye /> : <EyeOff />}
</button>
</div>
{message.text && (
<span
className={`message ${
message.type === 'success' ? 'successMsg' : ''
}`}
>
{message.text}
</span>
)}
<Button disabled={isPending} className='btn'>
{isPending ? 'Registering' : 'Register'}
</Button>
</form>
</div>
);
}
- useActionState를 사용하여 서버 응답(success, error)을 상태로 관리했습니다.
- showPassword 상태를 추가하여 사용자가 비밀번호를 토글할 수 있도록 했습니다.
- 회원가입이 성공하면 setTimeout을 사용해 일정 시간 후 로그인 화면으로 이동하도록 설정했습니다.
1-2. 회원가입 API 요청 함수
회원가입 요청을 서버로 보내는 함수는 register에서 처리했습니다.
회원가입 요청 시 서버에서 받은 응답을 처리하고, 성공 여부에 따라 상태를 반환하도록 했습니다.
export async function register(
previousState: RegisterState,
formData: FormData
): Promise<RegisterState> {
try {
const email = formData.get('email');
const password = formData.get('password');
const passwordCheck = formData.get('passwordCheck');
const res = await fetch('http://localhost:3000/api/user/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password, passwordCheck }),
});
const data = await res.json();
if (data?.error) {
return { ...previousState, error: data.error };
}
return { error: null, success: data };
} catch (error) {
return { ...previousState, error: 'Something is wrong' };
}
}
2. 로그인 기능 구현
로그인은 회원가입과 거의 동일한 방식으로 구현했습니다.
로그인 API 요청 함수 (login)
export async function login(
previousState: RegisterState,
formData: FormData
): Promise<RegisterState> {
try {
const email = formData.get('email');
const password = formData.get('password');
console.log({ email, password });
const res = await fetch('http://localhost:3000/api/user/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include', // 쿠키를 포함하여 요청
body: JSON.stringify({ email, password }),
});
const data = await res.json();
if (data?.error) {
return { ...previousState, error: data.error };
}
return { error: null, success: data };
} catch (error) {
return { ...previousState, error: 'Something is wrong' };
}
}
credentials: 'include'란?
로그인 요청을 보낼 때 credentials: 'include' 옵션을 추가했습니다.
- fetch 요청 시 credentials: 'include'를 설정하면 브라우저가 쿠키 및 인증 정보를 포함하여 요청을 보냅니다.
- JWT나 세션 기반 인증을 사용할 때 필수적인 설정입니다.
- 서버에서 CORS 정책을 적절히 설정해야 정상적으로 작동합니다.
'Node.js > Todo' 카테고리의 다른 글
Google 로그인 구현 (OAuth 2.0 + JWT + Zustand) (0) | 2025.02.27 |
---|---|
JWT 인증 방식 개선: 쿠키 기반에서 토큰 기반으로 전환 (0) | 2025.02.24 |
Todo CRUD API 구현하기 (0) | 2025.02.19 |
쿠키 기반 JWT 인증 및 로그인/로그아웃 기능 (0) | 2025.02.18 |
MongoDB 연결 및 회원가입 구현 (0) | 2025.02.17 |


회원가입 및 로그인 기능을 클라이언트에서 구현한 내용을 정리해보겠습니다.
1. 회원가입 기능 구현
회원가입 기능은 useActionState를 활용하여 서버 요청과 응답을 관리하도록 구현했습니다.
회원가입 요청을 보낼 때 상태를 관리하고, 성공 또는 실패 여부를 사용자에게 보여줄 수 있도록 했습니다.
1-1. 회원가입 컴포넌트
JAVASCRIPT
import { useActionState, useEffect, useState } from 'react';
import { Eye, EyeOff, UserRound } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { register } from '@/actions/userActions';
type Props = {
setIsRegister: React.Dispatch<React.SetStateAction<boolean>>;
};
export default function Register({ setIsRegister }: Props) {
const [formData, setFormData] = useState({
email: '',
password: '',
passwordCheck: '',
});
const [state, formAction, isPending] = useActionState(register, {
success: null,
error: null,
});
const [showPassword, setShowPassword] = useState({
password: false,
passwordCheck: false,
});
const toggleShowPassword = (field: 'password' | 'passwordCheck') => {
setShowPassword((prev) => ({ ...prev, [field]: !prev[field] }));
};
const [message, setMessage] = useState<{
text: string | null;
type: 'success' | 'error' | null;
}>({
text: null,
type: null,
});
useEffect(() => {
if (state.success) {
const timer = setTimeout(() => {
setIsRegister(false);
setFormData({ email: '', password: '', passwordCheck: '' });
}, 1000);
return () => clearTimeout(timer);
}
}, [state.success]);
useEffect(() => {
if (state.success || state.error) {
setMessage({
text: state.success || state.error,
type: state.success ? 'success' : 'error',
});
const timer = setTimeout(() => {
setMessage({ text: null, type: null });
state.success = null;
state.error = null;
}, 1000);
return () => clearTimeout(timer);
}
}, [state.success, state.error]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
return (
<div className='form-box register'>
<form action={formAction}>
<h1>Registration</h1>
<div className='input-box'>
<Input
type='email'
name='email'
placeholder='Email Address'
value={formData.email}
onChange={handleChange}
required
/>
<UserRound />
</div>
<div className='input-box'>
<Input
type={showPassword.password ? 'text' : 'password'}
name='password'
placeholder='Password'
value={formData.password}
onChange={handleChange}
required
/>
<button
type='button'
onClick={() => toggleShowPassword('password')}
className='flex items-center'
>
{showPassword.password ? <Eye /> : <EyeOff />}
</button>
</div>
<div className='input-box'>
<Input
type={showPassword.passwordCheck ? 'text' : 'password'}
name='passwordCheck'
placeholder='Password Check'
value={formData.passwordCheck}
onChange={handleChange}
required
/>
<button
type='button'
onClick={() => toggleShowPassword('passwordCheck')}
className='flex items-center'
>
{showPassword.passwordCheck ? <Eye /> : <EyeOff />}
</button>
</div>
{message.text && (
<span
className={`message ${
message.type === 'success' ? 'successMsg' : ''
}`}
>
{message.text}
</span>
)}
<Button disabled={isPending} className='btn'>
{isPending ? 'Registering' : 'Register'}
</Button>
</form>
</div>
);
}
- useActionState를 사용하여 서버 응답(success, error)을 상태로 관리했습니다.
- showPassword 상태를 추가하여 사용자가 비밀번호를 토글할 수 있도록 했습니다.
- 회원가입이 성공하면 setTimeout을 사용해 일정 시간 후 로그인 화면으로 이동하도록 설정했습니다.
1-2. 회원가입 API 요청 함수
회원가입 요청을 서버로 보내는 함수는 register에서 처리했습니다.
회원가입 요청 시 서버에서 받은 응답을 처리하고, 성공 여부에 따라 상태를 반환하도록 했습니다.
JAVASCRIPT
export async function register(
previousState: RegisterState,
formData: FormData
): Promise<RegisterState> {
try {
const email = formData.get('email');
const password = formData.get('password');
const passwordCheck = formData.get('passwordCheck');
const res = await fetch('http://localhost:3000/api/user/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password, passwordCheck }),
});
const data = await res.json();
if (data?.error) {
return { ...previousState, error: data.error };
}
return { error: null, success: data };
} catch (error) {
return { ...previousState, error: 'Something is wrong' };
}
}
2. 로그인 기능 구현
로그인은 회원가입과 거의 동일한 방식으로 구현했습니다.
로그인 API 요청 함수 (login)
JAVASCRIPT
export async function login(
previousState: RegisterState,
formData: FormData
): Promise<RegisterState> {
try {
const email = formData.get('email');
const password = formData.get('password');
console.log({ email, password });
const res = await fetch('http://localhost:3000/api/user/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include', // 쿠키를 포함하여 요청
body: JSON.stringify({ email, password }),
});
const data = await res.json();
if (data?.error) {
return { ...previousState, error: data.error };
}
return { error: null, success: data };
} catch (error) {
return { ...previousState, error: 'Something is wrong' };
}
}
credentials: 'include'란?
로그인 요청을 보낼 때 credentials: 'include' 옵션을 추가했습니다.
- fetch 요청 시 credentials: 'include'를 설정하면 브라우저가 쿠키 및 인증 정보를 포함하여 요청을 보냅니다.
- JWT나 세션 기반 인증을 사용할 때 필수적인 설정입니다.
- 서버에서 CORS 정책을 적절히 설정해야 정상적으로 작동합니다.
'Node.js > Todo' 카테고리의 다른 글
Google 로그인 구현 (OAuth 2.0 + JWT + Zustand) (0) | 2025.02.27 |
---|---|
JWT 인증 방식 개선: 쿠키 기반에서 토큰 기반으로 전환 (0) | 2025.02.24 |
Todo CRUD API 구현하기 (0) | 2025.02.19 |
쿠키 기반 JWT 인증 및 로그인/로그아웃 기능 (0) | 2025.02.18 |
MongoDB 연결 및 회원가입 구현 (0) | 2025.02.17 |