React์ useFormStatus ํ ์ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ํ์ฉํด ๋ณด์ธ์. ์ด ์ข ํฉ ๊ฐ์ด๋๋ ๊ธฐ์ด๋ถํฐ ๊ณ ๊ธ ์ฌ์ฉ๋ฒ๊น์ง, ์ค์ฉ์ ์ธ ์์ ์ ๊ธ๋ก๋ฒ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ชจ๋ ๋ค๋ฃน๋๋ค.
React useFormStatus ๋ง์คํฐํ๊ธฐ: ๊ธ๋ก๋ฒ ๊ฐ๋ฐ์๋ฅผ ์ํ ์ข ํฉ ๊ฐ์ด๋
๋์์์ด ๋ฐ์ ํ๋ ํ๋ก ํธ์๋ ๊ฐ๋ฐ ํ๊ฒฝ์์ ํผ ์ํ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๊ฒ์ ์ํํ ์ฌ์ฉ์ ๊ฒฝํ์ ๋งค์ฐ ์ค์ํฉ๋๋ค. React๋ ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ์ํคํ
์ฒ์ ๊ฐ๋ ฅํ ํ
์ ํตํด ๋ณต์กํ ๋ฌธ์ ์ ๋ํ ์ฐ์ํ ํด๊ฒฐ์ฑ
์ ์ ๊ณตํฉ๋๋ค. ๊ทธ์ค ํ๋๊ฐ useFormStatus
ํ
์ผ๋ก, React ์ํ๊ณ์ ๋น๊ต์ ์ต๊ทผ์ ์ถ๊ฐ๋์ด ํผ ์ ์ถ ์ํ ์ถ์ ์ ๋จ์ํํฉ๋๋ค. ์ด ๊ฐ์ด๋๋ useFormStatus
์ ๊ธฐ๋ณธ ์๋ฆฌ๋ถํฐ ๊ณ ๊ธ ์์ฉ๊น์ง ๋ชจ๋ ๊ฒ์ ๋ค๋ฃจ๋ฉฐ, ์ ์ธ๊ณ ๊ฐ๋ฐ์๋ฅผ ์ํ ์ค์ฉ์ ์ธ ์์ ๋ฅผ ์ ๊ณตํฉ๋๋ค.
React useFormStatus๋ ๋ฌด์์ธ๊ฐ?
React Router v6.4 ๋ฆด๋ฆฌ์ค์ ์ผ๋ถ๋ก ๋์
๋(๊ทธ๋ฆฌ๊ณ ๋์ค์ React ์์ฒด์ ํตํฉ๋) useFormStatus
ํ
์ ํผ ์ ์ถ ์ํ์ ๋ํ ์ค์๊ฐ ์
๋ฐ์ดํธ๋ฅผ ์ ๊ณตํ๋๋ก ์ค๊ณ๋์์ต๋๋ค. ์ด๋ฅผ ํตํด ๊ฐ๋ฐ์๋ ํผ์ด ํ์ฌ ์ ์ถ ์ค์ธ์ง, ์ฑ๊ณต์ ์ผ๋ก ์ ์ถ๋์๋์ง, ๋๋ ์ ์ถ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ๋์ง๋ฅผ ์ฝ๊ฒ ํ์ธํ ์ ์์ต๋๋ค. ์ด ์ ๋ณด๋ ์ฌ์ฉ์์๊ฒ ์๊ฐ์ ํผ๋๋ฐฑ์ ์ ๊ณตํ์ฌ ํผ๊ณผ์ ์ํธ์์ฉ ์ํ๋ฅผ ์ดํดํ๊ณ ์ ์ฌ์ ์ธ ๋ถํธํจ์ ๋ฐฉ์งํ๋ ๋ฐ ๋งค์ฐ ์ค์ํฉ๋๋ค. ๋ณธ์ง์ ์ผ๋ก ์ด๋ ํผ ์ ์ถ๊ณผ ๊ด๋ จ๋ ๋ก๋ฉ, ์ฑ๊ณต, ์ค๋ฅ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ํ์คํ๋ ๋ฐฉ๋ฒ์ผ๋ก, ๊ฐ๋ฐ ํ๋ก์ธ์ค๋ฅผ ๊ฐ์ํํฉ๋๋ค.
์ useFormStatus๋ฅผ ์ฌ์ฉํด์ผ ํ๋๊ฐ?
useFormStatus
๊ฐ ๋ฑ์ฅํ๊ธฐ ์ , ๊ฐ๋ฐ์๋ค์ ์ข
์ข
ํผ ์ํ๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํด ์์ฒด์ ์ธ ํด๊ฒฐ์ฑ
์ ์์กดํ์ต๋๋ค. ์ด๋ ์ผ๋ฐ์ ์ผ๋ก ๋ก๋ฉ ํ์๊ธฐ, ์ฑ๊ณต ๋ฉ์์ง, ์ค๋ฅ ํ์๋ฅผ ์ถ์ ํ๊ธฐ ์ํ ์ํ ๋ณ์๋ฅผ ๋ง๋๋ ๊ฒ์ ํฌํจํ์ต๋๋ค. ์ด๋ฌํ ์์ฒด ํด๊ฒฐ์ฑ
์ ๊ธฐ๋ฅ์ ์ด๊ธด ํ์ง๋ง ๋ฒ๊ฑฐ๋กญ๊ณ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ ์ฌ์ฐ๋ฉฐ, ์ข
์ข
์๋นํ ์์ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋๋ฅผ ํ์๋ก ํ์ต๋๋ค. useFormStatus
๋ ๋ด์ฅ๋ ํ์คํ๋ ์ ๊ทผ ๋ฐฉ์์ ์ ๊ณตํ์ฌ ์ด ๊ณผ์ ์ ๋จ์ํํฉ๋๋ค. ์ฃผ์ ์ฅ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ๋จ์ํ๋ ์ํ ๊ด๋ฆฌ: ํผ ์ ์ถ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐ ํ์ํ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋์ ์์ ์ค์ ๋๋ค.
- ํฅ์๋ ์ฌ์ฉ์ ๊ฒฝํ: ์ฌ์ฉ์์๊ฒ ๋ช ํํ ์๊ฐ์ ํผ๋๋ฐฑ์ ์ ๊ณตํ์ฌ ์ ๋ฐ์ ์ธ ํผ ์ํธ์์ฉ ๊ฒฝํ์ ํฅ์์ํต๋๋ค.
- ํฅ์๋ ์ฝ๋ ๊ฐ๋ ์ฑ: ํผ ๊ด๋ จ ๋ก์ง์ ๋ ๊ฐ๊ฒฐํ๊ณ ์ดํดํ๊ธฐ ์ฝ๊ฒ ๋ง๋ญ๋๋ค.
- ์ฉ์ดํ ์ ์ง๋ณด์: ํผ ๊ด๋ จ ์ฝ๋์ ์ ์ง๋ณด์ ๋ฐ ์์ ์ ๋จ์ํํฉ๋๋ค.
- ๋ด์ฅ ๊ธฐ๋ฅ: ๋ผ์ฐํ ์ปจํ ์คํธ ๋ด์์(๋๋ ์ ์ ํ ํตํฉ์ ํตํด ๊ทธ ๋ฐ์์๋) ํผ ์ ์ถ์ ์ฒ๋ฆฌํ๋๋ก ์ค๊ณ๋ React Router์ ๊ธฐ๋ฅ์ ํ์ฉํฉ๋๋ค.
useFormStatus ์ฌ์ฉ ๋ฐฉ๋ฒ: ์ค์ฉ์ ์ธ ์์
useFormStatus
์ฌ์ฉ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด ์ค์ฉ์ ์ธ ์์ ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์ฌ์ฉ์ ๋ฑ๋ก ์ ์ฐจ๋ฅผ ์๋ฎฌ๋ ์ด์
ํ์ฌ ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ถํ๋ ๊ฐ๋จํ ํผ์ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. ์ด ์์ ๋ ๋ค์ํ ๊ท๋ชจ์ ํ๋ก์ ํธ์์ ์์
ํ๋ ์ ์ธ๊ณ ๊ฐ๋ฐ์๋ค์๊ฒ ์ ์ฉ๋ ์ ์์ต๋๋ค.
import React from 'react';
import { useFormStatus } from 'react-dom'; // Or import from 'react-dom' if using React 18
function RegistrationForm() {
const { pending, method, action } = useFormStatus();
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
try {
const response = await fetch('/api/register', {
method: 'POST',
body: formData,
});
if (response.ok) {
// Handle successful registration (e.g., show a success message)
alert('Registration successful!');
} else {
// Handle registration failure (e.g., show an error message)
alert('Registration failed.');
}
} catch (error) {
// Handle network errors or other exceptions
console.error('Error during registration:', error);
alert('An error occurred during registration.');
}
}
return (
<form onSubmit={handleSubmit} action='/api/register' method='POST'>
<div>
<label htmlFor='name'>Name:</label>
<input type='text' id='name' name='name' required />
</div>
<div>
<label htmlFor='email'>Email:</label>
<input type='email' id='email' name='email' required />
</div>
<button type='submit' disabled={pending}>
{pending ? 'Registering...' : 'Register'}
</button>
{method && <p>Method used: {method}</p>}
{action && <p>Action used: {action}</p>}
</form>
);
}
export default RegistrationForm;
์ด ์์ ์์:
'react-dom'
์์useFormStatus
๋ฅผ ๊ฐ์ ธ์ต๋๋ค.RegistrationForm
์ปดํฌ๋ํธ ๋ด์์useFormStatus()
๋ฅผ ํธ์ถํ๋ฉด ํผ ์ํ์ ๋ํ ์ ๋ณด๊ฐ ๋ด๊ธด ๊ฐ์ฒด๊ฐ ๋ฐํ๋ฉ๋๋ค. ์ฃผ์ ์์ฑ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:pending
: ํผ์ด ํ์ฌ ์ ์ถ ์ค์ธ์ง ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ ๋ถ๋ฆฌ์ธ ๊ฐ์ ๋๋ค.method
: 'POST'๋ 'GET'๊ณผ ๊ฐ์ ํผ์ ์ ์ถ ๋ฐฉ์์ ๋๋ค.action
: ํผ์ด ์ ์ถ๋๋ URL์ ๋๋ค.handleSubmit
ํจ์๋ ํผ์ด ์ ์ถ๋ ๋ ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค. ์ด ํจ์๋ ๊ธฐ๋ณธ ํผ ์ ์ถ ๋์์ ๋ง๊ณfetch
๋ฅผ ์ฌ์ฉํ์ฌ API ์์ฒญ์ ์๋ฎฌ๋ ์ด์ ํฉ๋๋ค.- ์ ์ถ ๋ฒํผ์
disabled
์์ฑ์pending
์ผ๋ก ์ค์ ๋์ด, ์งํ ์ค์ผ ๋ ์ฌ์ฉ์๊ฐ ํผ์ ๋ค์ ์ ์ถํ๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค. - ๋ฒํผ์ ํ ์คํธ๋ ํผ์ ์ ์ถ ์ํ๋ฅผ ๋ํ๋ด๊ธฐ ์ํด ๋์ ์ผ๋ก ์ ๋ฐ์ดํธ๋ฉ๋๋ค (์: "๋ฑ๋ก ์ค...").
์ด ๊ธฐ๋ณธ ์์ ๋ ๋ค์ํ ๊ตญ์ ํ๋ก์ ํธ์ ์ฌ๋ฌ ํผ ์๋๋ฆฌ์ค์ ์ฝ๊ฒ ์ ์ฉํ ์ ์์ต๋๋ค. API ์๋ํฌ์ธํธ(์ด ์์ ์์๋ /api/register
)์ ํผ ํ๋๋ฅผ ์ ํ๋ฆฌ์ผ์ด์
์ ํน์ฑ์ ๋ง๊ฒ ์กฐ์ ํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
useFormStatus ๊ณ ๊ธ ๊ธฐ์
๊ธฐ๋ณธ์ ์ธ ๊ตฌํ์ ๋์ด useFormStatus
๋ ๋ ์ ๊ตํ ๋ฐฉ์์ผ๋ก ์ฌ์ฉ๋ ์ ์์ต๋๋ค. ๋ช ๊ฐ์ง ๊ณ ๊ธ ๊ธฐ์ ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
1. ํผ ์ ํจ์ฑ ๊ฒ์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํตํฉํ๊ธฐ
ํผ ์ ํจ์ฑ ๊ฒ์ฌ๋ ๋ชจ๋ ์น ์ ํ๋ฆฌ์ผ์ด์
์ ์ค์ํ ์ธก๋ฉด์ผ๋ก, ์ฌ์ฉ์ ์
๋ ฅ์ด ๋ฏธ๋ฆฌ ์ ์๋ ๊ธฐ์ค์ ์ถฉ์กฑํ๋์ง ํ์ธํฉ๋๋ค. Formik, Yup, Zod์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ปค์คํ
์ ํจ์ฑ ๊ฒ์ฌ ๋ก์ง์ useFormStatus
์ ์ํํ๊ฒ ํตํฉํ ์ ์์ต๋๋ค. ์ด๋ฌํ ํตํฉ์ ํตํด ํผ์ ์ํ๋ฅผ ๋ ์ ๋ฐํ๊ฒ ์ ์ดํ๊ณ ๋ ๋์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ณด๋ฅ ์ํ์ ํผ ํ๋์ ์ ํจ์ฑ์ ๋ชจ๋ ๊ธฐ๋ฐ์ผ๋ก ์ ์ถ ๋ฒํผ์ ํ์ฑํ/๋นํ์ฑํํ ์ ์์ต๋๋ค.
import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import { useFormStatus } from 'react-dom';
function RegistrationForm() {
const { pending } = useFormStatus();
const formik = useFormik({
initialValues: {
name: '',
email: '',
password: '',
},
validationSchema: Yup.object({
name: Yup.string().required('Name is required'),
email: Yup.string().email('Invalid email address').required('Email is required'),
password: Yup.string().min(8, 'Password must be at least 8 characters').required('Password is required'),
}),
onSubmit: async (values, { setSubmitting }) => {
try {
// Simulate an API call
await new Promise(resolve => setTimeout(resolve, 1000));
alert('Registration successful!');
} catch (error) {
// Handle errors
alert('Registration failed.');
} finally {
setSubmitting(false);
}
},
});
return (
<form onSubmit={formik.handleSubmit} action='/api/register' method='POST'>
<div>
<label htmlFor='name'>Name:</label>
<input type='text' id='name' name='name' onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.name} />
{formik.touched.name && formik.errors.name ? <div>{formik.errors.name}</div> : null}
</div>
<div>
<label htmlFor='email'>Email:</label>
<input type='email' id='email' name='email' onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.email} />
{formik.touched.email && formik.errors.email ? <div>{formik.errors.email}</div> : null}
</div>
<div>
<label htmlFor='password'>Password:</label>
<input type='password' id='password' name='password' onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.password} />
{formik.touched.password && formik.errors.password ? <div>{formik.errors.password}</div> : null}
</div>
<button type='submit' disabled={formik.isSubmitting || pending}>
{formik.isSubmitting || pending ? 'Registering...' : 'Register'}
</button>
</form>
);
}
export default RegistrationForm;
์ด ์์ ์์๋ ํผ ๊ด๋ฆฌ๋ฅผ ์ํด Formik์, ์คํค๋ง ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํด Yup์ ํตํฉํ์ต๋๋ค. ์ ์ถ ๋ฒํผ์ ํผ์ด ์ ์ถ ์ค(formik.isSubmitting
)์ด๊ฑฐ๋ ํผ ์ ์ถ์ด ๋ณด๋ฅ ์ค(useFormStatus
์ pending
)์ผ ๋ ๋นํ์ฑํ๋์ด, ํด๋ผ์ด์ธํธ ์ธก๊ณผ ์๋ฒ ์ธก ์์
๋ชจ๋์ ๋ํด ํตํฉ๋ ์ํ ๊ด๋ฆฌ๋ฅผ ์ ๊ณตํฉ๋๋ค.
2. ์งํ๋ฅ ํ์๊ธฐ ๋ณด์ฌ์ฃผ๊ธฐ
ํผ ์ ์ถ ์ค์ ์๊ฐ์ ํผ๋๋ฐฑ์ ์ ๊ณตํ๋ ๊ฒ์ ๊ธ์ ์ ์ธ ์ฌ์ฉ์ ๊ฒฝํ์ ๋งค์ฐ ์ค์ํฉ๋๋ค. ํนํ ํ์ผ ์
๋ก๋, ๊ฒฐ์ ์ฒ๋ฆฌ ๋๋ ์๊ฒฉ API์์ ์ํธ์์ฉ๊ณผ ๊ฐ์ด ์๊ฐ์ด ๊ฑธ๋ฆฌ๋ ์์
์ ์ฒ๋ฆฌํ ๋ ๊ทธ๋ ์ต๋๋ค. useFormStatus
๋ฅผ ์ฌ์ฉํ๋ฉด ๋ก๋ฉ ์คํผ๋๋ ์งํ๋ฅ ํ์์ค๊ณผ ๊ฐ์ ์งํ๋ฅ ํ์๊ธฐ๋ฅผ ํ์ํ์ฌ ์ฌ์ฉ์์๊ฒ ์์ฒญ์ด ์ฒ๋ฆฌ ์ค์์ ์๋ฆด ์ ์์ต๋๋ค. ์ด๋ฌํ ์๊ฐ์ ์ ํธ๋ ์ฌ์ฉ์์ ํ๋์ด ์ธ์ง๋๊ณ ์์์ ์์ฌ์ํค๊ณ , ์ฌ์ฉ์๊ฐ ํผ์ ์กฐ๊ธฐ์ ํฌ๊ธฐํ๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค. ์ด๋ ์ธํฐ๋ท ์ฐ๊ฒฐ์ด ๋๋ฆฌ๊ฑฐ๋ ์ฑ๋ฅ์ด ๋ฎ์ ์ฅ์น๋ฅผ ์ฌ์ฉํ๋ ๊ตญ๊ฐ์์ ํนํ ์ค์ํฉ๋๋ค.
import React from 'react';
import { useFormStatus } from 'react-dom';
function FileUploadForm() {
const { pending } = useFormStatus();
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
if (response.ok) {
alert('File uploaded successfully!');
} else {
alert('File upload failed.');
}
} catch (error) {
console.error('Upload error:', error);
alert('An error occurred during file upload.');
}
}
return (
<form onSubmit={handleSubmit} action='/api/upload' method='POST'>
<input type='file' name='file' />
<button type='submit' disabled={pending}>
{pending ? 'Uploading...' : 'Upload'}
</button>
{pending && <div>Uploading... <img src='/loading.gif' alt='Loading...' /></div>}
</form>
);
}
export default FileUploadForm;
์ด ์์ ์์๋ pending
์ด true์ธ ๋์ ๊ฐ๋จํ ๋ก๋ฉ ์คํผ๋๊ฐ ํ์๋์ด ์ฌ์ฉ์์ ์งํ๋ฅ ์ธ์์ ํฅ์์ํต๋๋ค. ๋ค์ํ ์ฌ์ฉ์ ๊ธฐ๋ฐ์ ๋ง์ถฐ ์ด๋ฌํ ๋ฉ์์ง์ ๋ํ ๊ตญ์ ํ(i18n)๋ฅผ ๊ณ ๋ คํ์ธ์. ์ด๋ i18next
๋ react-intl
๊ณผ ๊ฐ์ i18n ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌ์ฑํ ์ ์์ต๋๋ค.
3. ํผ ์ด๊ธฐํ ๋ฐ ์ฑ๊ณต/์ค๋ฅ ์ํ ์ฒ๋ฆฌํ๊ธฐ
ํผ ์ ์ถ์ด ์ฑ๊ณตํ ํ์๋ ์ข
์ข
ํผ์ ์ด๊ธฐํํ๊ณ ์ฑ๊ณต ๋ฉ์์ง๋ฅผ ํ์ํ๋ ๊ฒ์ด ๋ฐ๋์งํฉ๋๋ค. ๋ฐ๋๋ก ์ ์ถ์ด ์คํจํ์ ๋๋ ์ ์ ํ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ ๊ณตํด์ผ ํฉ๋๋ค. useFormStatus
๋ ํผ ์ด๊ธฐํ ๋ฐ ์ํ ๊ด๋ฆฌ ๊ธฐ์ ๊ณผ ํตํฉํ์ฌ ์ด๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ํํ ์ ์์ต๋๋ค.
import React, { useState } from 'react';
import { useFormStatus } from 'react-dom';
function ContactForm() {
const { pending } = useFormStatus();
const [submissionResult, setSubmissionResult] = useState(null);
async function handleSubmit(event) {
event.preventDefault();
setSubmissionResult(null);
const formData = new FormData(event.currentTarget);
try {
const response = await fetch('/api/contact', {
method: 'POST',
body: formData,
});
if (response.ok) {
setSubmissionResult({ success: true, message: 'Message sent successfully!' });
event.target.reset(); // Reset the form on success
} else {
const errorData = await response.json(); // Assuming the API returns JSON error
setSubmissionResult({ success: false, message: errorData.message || 'Failed to send message.' });
}
} catch (error) {
console.error('Error sending message:', error);
setSubmissionResult({ success: false, message: 'An unexpected error occurred.' });
}
}
return (
<form onSubmit={handleSubmit} action='/api/contact' method='POST'>
<div>
<label htmlFor='name'>Name:</label>
<input type='text' id='name' name='name' required />
</div>
<div>
<label htmlFor='email'>Email:</label>
<input type='email' id='email' name='email' required />
</div>
<div>
<label htmlFor='message'>Message:</label>
<textarea id='message' name='message' required />
</div>
<button type='submit' disabled={pending}>
{pending ? 'Sending...' : 'Send'}
</button>
{submissionResult && (
<div className={submissionResult.success ? 'success' : 'error'}>
{submissionResult.message}
</div>
)}
</form>
);
}
export default ContactForm;
์ฌ๊ธฐ์๋ submissionResult
์ํ ๋ณ์๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ถ์ ์ฑ๊ณต ๋๋ ์คํจ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค. ์ฑ๊ณต ์์๋ event.target.reset()
์ ์ฌ์ฉํ์ฌ ํผ์ ์ด๊ธฐํํ๊ณ ์ฑ๊ณต ๋ฉ์์ง๋ฅผ ํ์ํฉ๋๋ค. ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ์๋ ์ฌ์ฉ์์๊ฒ ์ค๋ฅ ๋ฉ์์ง๊ฐ ํ์๋ฉ๋๋ค. ๋ค์ํ ๋ฌธํ์ ๋์์ธ ์ ํธ๋์ ๊ฑธ์ณ ํผ๋๋ฐฑ์ ๋ ํจ๊ณผ์ ์ผ๋ก ์ ๋ฌํ๊ธฐ ์ํด ์ฑ๊ณต ๋ฉ์์ง์ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์๊ฐ์ ์ผ๋ก ๊ตฌ๋ถํ๋ ์ ์ ํ ์คํ์ผ๋ง์ ์ฌ์ฉํ๋ ๊ฒ์ ์์ง ๋ง์ธ์. ์ ์ ํ ์คํ์ผ๋ง์ CSS๋ CSS-in-JS ๋ผ์ด๋ธ๋ฌ๋ฆฌ(์: styled-components)๋ฅผ ์ฌ์ฉํ์ฌ ํตํฉํ ์ ์์ต๋๋ค.
4. ๋ผ์ฐํธ ์ ํ๊ณผ ํตํฉํ๊ธฐ (๊ณ ๊ธ)
React ์ ํ๋ฆฌ์ผ์ด์
์์ ๋ผ์ฐํฐ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, useFormStatus
๋ฅผ ๋ผ์ฐํธ ์ ํ๊ณผ ํจ๊ป ํ์ฉํ์ฌ ํผ ์ ์ถ ์ค ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํฌ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ํผ์ด ์ ์ถ๋๋ ๋์ ๋ก๋ฉ ํ์๊ธฐ๋ฅผ ํ์ํ๊ณ ์ ์ถ์ด ์๋ฃ๋ ๋๊น์ง ๋ด๋น๊ฒ์ด์
์ ๋ง์ ์ ์์ต๋๋ค. ์ด๋ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ์ ๋ณด์ฅํ๊ณ ์ฌ์ฉ์๊ฐ ํผ ์ ์ถ ํ๋ก์ธ์ค๊ฐ ์๋ฃ๋๊ธฐ ์ ์ ํ์ด์ง๋ฅผ ๋ ๋๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค. ์ด๋ React Router์ Await
์ปดํฌ๋ํธ์ ๊ฐ์ ์์คํ
๊ณผ ํตํฉํ ๋ ํนํ ์ ์ฉํฉ๋๋ค. ์ด๋ฌํ ํตํฉ์ ๋คํธ์ํฌ ์ง์ฐ ์๊ฐ์ด ๋ฌธ์ ๊ฐ ๋ ์ ์๋ ๊ตญ์ ์ฑ์์ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํฌ ์ ์์ต๋๋ค.
๊ธ๋ก๋ฒ ๊ฐ๋ฐ์๋ฅผ ์ํ ๋ชจ๋ฒ ์ฌ๋ก
useFormStatus
๊ฐ ํผ ์ํ ๊ด๋ฆฌ๋ฅผ ๋จ์ํํ์ง๋ง, ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์ฑํํ๋ฉด ๊ฒฌ๊ณ ํ๊ณ ์ ์ธ๊ณ์ ์ผ๋ก ์นํ์ ์ธ ๊ตฌํ์ ๋ณด์ฅํ ์ ์์ต๋๋ค:
- ์ ๊ทผ์ฑ: ํผ์ด ์ฅ์ ๊ฐ ์๋ ์ฌ์ฉ์์๊ฒ ์ ๊ทผ ๊ฐ๋ฅํ๋๋ก ๋ณด์ฅํ์ธ์. ์ ์ ํ ARIA ์์ฑ, ์๋งจํฑ HTML์ ์ฌ์ฉํ๊ณ ์ถฉ๋ถํ ์์ ๋๋น๋ฅผ ์ ๊ณตํ์ธ์. ์ด๋ ๋ง์ ๊ตญ๊ฐ์์ ๋ฒ์ ์๊ตฌ์ฌํญ(์: ๋ฏธ๊ตญ์ ์ฅ์ ์ธ๋ฒ, ADA)์ด๋ฉฐ ๋ ํฌ์ฉ์ ์ธ ์ฌ์ฉ์ ๊ฒฝํ์ ์กฐ์ฑํฉ๋๋ค.
- ๊ตญ์ ํ(i18n):
i18next
,react-intl
๊ณผ ๊ฐ์ i18n ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ํผ ๋ ์ด๋ธ, ์ค๋ฅ ๋ฉ์์ง, ์ฑ๊ณต ๋ฉ์์ง๋ฅผ ์ฌ๋ฌ ์ธ์ด๋ก ๋ฒ์ญํ์ธ์. ์ฌ์ฉ์ ๋ก์บ์ ๋ฐ๋ผ ๋ ์ง, ์๊ฐ, ํตํ ํ์์ ์ ์ ํ๊ฒ ํ์ํ์ธ์. ์ด๋ ์ ์ธ๊ณ ์ฌ์ฉ์๋ค์ด ํผ๊ณผ ๊ทธ๋ค์ด ๋ฐ๋ ํผ๋๋ฐฑ์ ์ดํดํ ์ ์๋๋ก ํ๋ ๊ธ๋ก๋ฒ ์ฌ์ฉ์ ๊ธฐ๋ฐ์ ๊ฐ์ง ์ ํ๋ฆฌ์ผ์ด์ ์ ๋งค์ฐ ์ค์ํฉ๋๋ค. - ํ์งํ(l10n): ๋ฒ์ญ์ ๋์ด ๋ฌธํ์ ๋์์ค๋ฅผ ๊ณ ๋ คํ์ธ์. ๋์ ๊ณ ๊ฐ์ ๋ฌธํ์ ์ ํธ๋์ ๋ฐ๋ผ ํผ ๋ ์ด์์๊ณผ ํ๋ฆ์ ๋์์ธํ์ธ์. ์ค๋ฅธ์ชฝ์์ ์ผ์ชฝ์ผ๋ก ์ฐ๋(RTL) ์ธ์ด๋ฅผ ๊ณ ๋ คํ๊ณ ๊ทธ์ ๋ง๊ฒ ๋์์ธ์ ์กฐ์ ํ์ธ์. ์ฌ์ฉ์์ ๊ตญ๊ฐ/์ง์ญ์ ๋ง๋ ํ์ค ์ ํ๋ฒํธ ์์์ ์ฌ์ฉํ๋ ์ ํ๋ฒํธ ์ ๋ ฅ ํ๋๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์.
- ์ค๋ฅ ์ฒ๋ฆฌ: ํฌ๊ด์ ์ธ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํ์ธ์. ์ดํดํ๊ธฐ ์ฝ๊ณ ๋ช ํํ๋ฉฐ ๊ฐ๊ฒฐํ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ ๊ณตํ์ธ์. ํด๋ผ์ด์ธํธ ์ธก๊ณผ ์๋ฒ ์ธก ๋ชจ๋์์ ์ฌ์ฉ์ ์ ๋ ฅ์ ๊ฒ์ฆํ์ธ์. ์ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๊ณ ์ฌ์ฉ์๊ฐ ์ค์๋ฅผ ์์ ํ๋ ๋ฐ ๋์์ ์ค๋๋ค. ๊ตฌ์ฒด์ ์ด๊ณ ํ์งํ๋ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ ๊ณตํด์ผ ํฉ๋๋ค.
- ์ฑ๋ฅ ์ต์ ํ: ํนํ ์ธํฐ๋ท ์ฐ๊ฒฐ์ด ๋๋ฆฌ๊ฑฐ๋ ์ฑ๋ฅ์ด ๋ฎ์ ์ฅ์น๋ฅผ ์ฌ์ฉํ๋ ์ฌ์ฉ์๋ฅผ ์ํด ํผ์ ์ฑ๋ฅ์ ์ต์ ํํ์ฌ ์ํํ ์ฌ์ฉ์ ๊ฒฝํ์ ๋ณด์ฅํ์ธ์. ์ฌ๊ธฐ์๋ API ํธ์ถ ์ต์ ํ, ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง ์ต์ํ, ํจ์จ์ ์ธ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ ๊ธฐ์ ์ฌ์ฉ์ด ํฌํจ๋ฉ๋๋ค. ์ฝ๋ ๋ถํ ์ ๊ณ ๋ คํ์ธ์.
- ๋ณด์: ์ฌ์ดํธ ๊ฐ ์คํฌ๋ฆฝํ (XSS) ๋ฐ ์ฌ์ดํธ ๊ฐ ์์ฒญ ์์กฐ(CSRF)์ ๊ฐ์ ๋ณด์ ์ํ์ผ๋ก๋ถํฐ ํผ์ ๋ณดํธํ์ธ์. ์ฌ์ฉ์ ์ ๋ ฅ์ ์ด๊ท (sanitize)ํ๊ณ ์๋ฒ ์ธก์์ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํ์ธ์. ์ ์ ํ ์ธ์ฆ ๋ฐ ๊ถํ ๋ถ์ฌ ๋ฉ์ปค๋์ฆ์ ๊ตฌํํ์ธ์.
- ํ ์คํธ: ํผ์ด ์์๋๋ก ์๋ํ๋์ง ํ์ธํ๊ธฐ ์ํด ๋จ์ ํ ์คํธ์ ํตํฉ ํ ์คํธ๋ฅผ ์์ฑํ์ธ์. ๋ค์ํ ๋ธ๋ผ์ฐ์ ์ ์ฅ์น์์ ํผ์ ํ ์คํธํ์ธ์. ์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ๋ขฐ์ฑ์ ๋ณด์ฅํฉ๋๋ค. ์ฌ์ฉ์ฑ์ ๊ทน๋ํํ๊ธฐ ์ํด ๊ด๋ฒ์ํ ๊ธ๋ก๋ฒ ์ฅ์น ๋ฐ ํ๋ฉด ํฌ๊ธฐ์์ ํ ์คํธํ๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์.
- ์ฌ์ฉ์ ํผ๋๋ฐฑ: ํญ์ ์ฌ์ฉ์ ํผ๋๋ฐฑ์ ๊ท๋ฅผ ๊ธฐ์ธ์ด๊ณ ๊ทธ๋ค์ ๊ฒฝํ์ ๋ฐํ์ผ๋ก ํผ์ ์กฐ์ ํ์ธ์. ๋ถ์ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์๊ฐ ํผ๊ณผ ์ํธ์์ฉํ๋ ๋ฐฉ์์ ์ถ์ ํ๊ณ ๊ฐ์ ํ ์์ญ์ ์๋ณํ์ธ์.
- ์ ์ง์ ํฅ์: JavaScript๊ฐ ๋นํ์ฑํ๋ ๊ฒฝ์ฐ์๋ ํผ์ด ์๋ํ๋๋ก ๋์์ธํ์ธ์. JavaScript๋ฅผ ์ฌ์ฉํ ์ ์๋ ๊ฒฝ์ฐ ๋์ฒด ๋ฉ์ปค๋์ฆ(์: ์๋ฒ ์ธก ๋ ๋๋ง ๋ฒ์ ์ ํผ)์ ์ ๊ณตํ์ธ์. ์ด๋ ๋ค์ํ ๊ธ๋ก๋ฒ ์ฌ์ฉ์ ํ๊ฒฝ์์ ์ต๋ํ์ ํธํ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
- ๋น๋๊ธฐ ์์
: API ํธ์ถ๊ณผ ๊ฐ์ ๋น๋๊ธฐ ์์
์ ์ฒ๋ฆฌํ ๋
useFormStatus
์pending
์ํ๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์์๊ฒ ์๊ฐ์ ํผ๋๋ฐฑ์ ์ ๊ณตํ์ธ์. ์ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๊ณ ์ฌ์ฉ์๊ฐ ํผ์ ์ฌ๋ฌ ๋ฒ ์ ์ถํ๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.
๊ฒฐ๋ก
useFormStatus
๋ ๋ชจ๋ ๊ท๋ชจ์ ์ ํ๋ฆฌ์ผ์ด์
์์ ์์
ํ๋ React ๊ฐ๋ฐ์์๊ฒ ๊ท์คํ ๋๊ตฌ์
๋๋ค. ํผ ์ํ ๊ด๋ฆฌ์ ๋ํ ํ์คํ๋๊ณ ๋จ์ํ๋ ์ ๊ทผ ๋ฐฉ์์ ์ ๊ณตํจ์ผ๋ก์จ ์ฝ๋ ๊ฐ๋
์ฑ์ ๋์ด๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๋ฉฐ ๊ฐ๋ฐ ํ๋ก์ธ์ค๋ฅผ ๊ฐ์ํํฉ๋๋ค. ๋ก๋ฉ ์ํ ์ฒ๋ฆฌ ๋ฐ ์งํ๋ฅ ํ์๊ธฐ ํ์๋ถํฐ ์ ํจ์ฑ ๊ฒ์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ํตํฉ ๋ฐ ์ฑ๊ณต/์ค๋ฅ ๋ฉ์์ง ๊ด๋ฆฌ์ ์ด๋ฅด๊ธฐ๊น์ง, useFormStatus
๋ ํ๋ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ ์ํ ๋ค์ฌ๋ค๋ฅํ ๋๊ตฌ์
๋๋ค. ์ด ๊ฐ์ด๋์ ์ค๋ช
๋ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์ค์ํจ์ผ๋ก์จ ๊ฐ๋ฐ์๋ ์ ์ธ๊ณ ์ฌ์ฉ์์ ์๊ตฌ์ ๋ถ์ํ๋ ๊ฒฌ๊ณ ํ๊ณ ์ ๊ทผ ๊ฐ๋ฅํ๋ฉฐ ๊ธ๋ก๋ฒ ์นํ์ ์ธ ํผ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค. ์ด๋ฌํ ์์น์ ์์ฉํ๋ ๊ฒ์ ์ ์ธ๊ณ์ ๋ค์ํ ๋ฐฐ๊ฒฝ๊ณผ ๋ฌธํ๋ฅผ ๊ฐ์ง ์ฌ์ฉ์๋ค์ด ์ ๊ทผํ ์ ์๋ ์ฌ์ฉ์ ์นํ์ ์ด๊ณ ์ฑ๊ณต์ ์ธ React ์ ํ๋ฆฌ์ผ์ด์
์ ๋ง๋๋ ๋ฐ ํฌ๊ฒ ๊ธฐ์ฌํ ๊ฒ์
๋๋ค.