2025. 8. 13. 22:26ใReact
์ต์ข UI

์ด๋ฒคํธ ๋์ ์ ์ํ๊ธฐ
ํ๋์: start / end
๋ ธ๋์: ์กฐ๊ฑด
์ด๋ก์: ์กฐ๊ฑด ์ฑ๊ณต ์ ์ํ๋๋ ์์
๋นจ๊ฐ์: ์กฐ๊ฑด ์คํจ ์ ์ํ๋๋ ์์

๋น๋ฐ๋ฒํธ ์ํธํ
๋น๋ฐ๋ฒํธ ์ ๋ ฅ ์ ์๋์ผ๋ก ์ํธํ๊ฐ ์งํ๋๊ณ ์์ ์ฒดํฌ๋ฐ์ค ํด๋ฆญ ์ ์ ๋ ฅํ ๋น๋ฐ๋ฒํธ๊ฐ ๋ณด์ด๋๋ก ํฉ๋๋ค.

ํด๋ผ์ด์ธํธ ์๋ฌ ์ถ๋ ฅ
1. ์์ด๋ ๋ฐ ๋น๋ฐ๋ฒํธ ๋ฏธ์ ๋ ฅ
๋ฏธ์ ๋ ฅ ํ ๋ก๊ทธ์ธ ์๋ ์ ์๋์ ๊ฐ์ด ๋นจ๊ฐ ๊ฒฝ๊ณ ๋ฌธ์ด ์ถ๋ ฅ๋๋๋ก ๊ตฌํํ ์์ ์ ๋๋ค.

2. ์กด์ฌํ์ง ์์ ์์ด๋ & ๋น๋ฐ๋ฒํธ ๋ถ์ผ์น
์กด์ฌํ์ง ์์ ์์ด๋์ด๊ฑฐ๋ ์ฌ๋ฐ๋ฅด์ง ์์ ๋น๋ฐ๋ฒํธ๋ก ๋ก๊ทธ์ธ ์๋ ์ ์๋์ ๊ฐ์ด ๋นจ๊ฐ ๊ฒฝ๊ณ ๋ฌธ์ด ์ถ๋ ฅ๋๋ฉฐ, ๋น๋ฐ๋ฒํธ ์ ๋ ฅ๋์ ์๋ ๋ฐ์ดํฐ๊ฐ ์ด๊ธฐํ๋ฉ๋๋ค.
๐ก ๊ฒฝ๊ณ ๋ฌธ์ด “์์ด๋ ๋๋ ๋น๋ฐ๋ฒํธ ๋ถ์ผ์น”์ธ ์ด์
์์ด๋ ์กด์ฌ ์ฌ๋ถ๋ฅผ ๊ณต๊ฒฉ์์๊ฒ ๋ ธ์ถํ์ง ์๊ธฐ ์ํด ์คํจ ์ ๋ฉ์์ง๋ฅผ ํตํฉํ์์ต๋๋ค.

๋ก๊ทธ์ธ ์ฑ๊ณต
๋ก๊ทธ์ธ ์ฑ๊ณต ์์๋ ๋ฉ์ธํ์ด์ง๋ก ์ด๋ํ๋ฉฐ ๋ฐฑ์๋์์ ๋ฐ๊ธํด ์ค accessToken์ ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ์ ์ฅํฉ๋๋ค.
๋ก์ปฌ ์คํ ๋ฆฌ์ง ์ ํ ์ด์ ๋ ์๋์ ์ค๋ช ํ ์์ ์ ๋๋ค.

ํ์๊ฐ์ ๋ฒํผ ํด๋ฆญ
ํ์๊ฐ์ ๋ฒํผ ํด๋ฆญ ์ ํ์๊ฐ์ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.

์ด๋ฒคํธ ์ฒ๋ฆฌ ๊ตฌํํ๊ธฐ
๋น๋ฐ๋ฒํธ ์ํธํ ๊ตฌํ
๐์ฐธ๊ณ ํ ์๋ฃ
[React] ๋ก๊ทธ์ธ ํ์ด์ง ๋น๋ฐ๋ฒํธ ๋ณด๊ธฐ/์จ๊ธฐ๊ธฐ ๊ตฌํํ๊ธฐ! - input type ๋ณ๊ฒฝ
๋น๋ฐ๋ฒํธ ๋ณด๊ธฐ/์จ๊ธฐ๊ธฐ ๋ก๊ทธ์ธ ํ์ด์ง์์ ์ด์ ๋ ํ์๊ฐ ๋์ด๋ฒ๋ฆฐ ๊ธฐ๋ฅ์ธ ์ ๋ ฅํ๋ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ณด๊ฑฐ๋ ์จ๊ธฐ๋ ๊ธฐ๋ฅ์ ์์ฃผ ๊ฐ๋จํ๊ฒ ๊ตฌํ์ ํด๋ณด์์ต๋๋ค. ๋ฆฌ์กํธ๋ก ์์ ์ ํ๊ธฐ ๋๋ฌธ์ ์ฝ๊ฐ
shape-coding.tistory.com
๐๋น๋ฐ๋ฒํธ ์จ๊ธฐ๊ธฐ
input ํ๊ทธ์ type์ password๋ก ์ค์ ํ์ฌ ๋น๋ฐ๋ฒํธ๋ฅผ ์จ๊ธธ ์ ์์ต๋๋ค.
<input
type="password"
/>
๐ ๋น๋ฐ๋ฒํธ ํ์ํ๊ธฐ
์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๋น๋ฐ๋ฒํธ๋ฅผ ํ์ธํ๊ณ ์ถ์ ๋๊ฐ ์์ต๋๋ค. ๋ฐ๋ผ์, ๋น๋ฐ๋ฒํธ ๋ณด๊ธฐ ์ฒดํฌ ๋ฐ์ค๋ฅผ ๋ง๋ค์ด ์ฒดํฌ ์ ๋น๋ฐ๋ฒํธ ์ํธํ๋ฅผ ํด์ ํ ์ ์๋๋ก ํ์์ต๋๋ค.
function SignInField({ baseField, showPassword, setShowPassword, setSignInInfo }) {
const isPassword = baseField.type === "password";
const isError = baseField.error !== "";
return (
<div className="flex flex-col">
<div className="flex items-center mb-5">
...์๋ต
{/* ์ฒดํฌ ๋ฐ์ค ํ์ ๋ถ๋ถ */}
{
isPassword ?
<label className="flex items-center mx-5">
<input
type="checkbox"
className="mx-2"
checked={showPassword}
onChange={(e) => setShowPassword(e.target.checked)}
/>
๋น๋ฐ๋ฒํธ ๋ณด๊ธฐ
</label> : null
}
</div>
... ์๋ต
);
}
๐ ๋์ ์๋ฆฌ
- ์ฒดํฌ๋ฐ์ค ํด๋ฆญ: ์ฌ์ฉ์๊ฐ "๋น๋ฐ๋ฒํธ ๋ณด๊ธฐ" ์ฒดํฌ๋ฐ์ค๋ฅผ ํด๋ฆญ (false → true)
- ์ด๋ฒคํธ ๋ฐ์: onChange ์ด๋ฒคํธ ํธ๋ฆฌ๊ฑฐ
- ์ํ ์ฝ๊ธฐ: ์ด๋ฒคํธ ํธ๋ค๋ฌ์์ e.target.checked๋ก ๋ณ๊ฒฝ๋ ์ํ(true) ํ์ธ
- ์ํ ์ ๋ฐ์ดํธ: setShowPassword๋ก ์ํ ์ ๋ฐ์ดํธ
- UI ๋ฐ์: checked={showPassword}๊ฐ true๊ฐ ๋์ด ์ฒดํฌ๋ฐ์ค๊ฐ ์ฒดํฌ๋ ์ํ๋ก ํ์
- ํ์ ๋ณ๊ฒฝ: isPassword && showPassword๊ฐ true๊ฐ ๋์ด input ํ์ ์ด "text"๋ก ๋ณ๊ฒฝ๋๋ฉฐ ๋น๋ฐ๋ฒํธ๊ฐ ํ์
function SignInField({ baseField, showPassword, setShowPassword, setSignInInfo }) {
const isPassword = baseField.type === "password";
const isError = baseField.error !== "";
return (
<div className="flex flex-col">
<div className="flex items-center mb-5">
<label className="w-24 text-left mr-4">{baseField.label}</label>
<input
name={baseField.name}
{/*type์ด password ์ด๊ณ showPassword๊ฐ true๊ฐ ๋๋ฉฐ ์ํธํ๊ฐ ํด์ ๋ฉ๋๋ค.*/}
type={isPassword && showPassword ? "text" :baseField.type}
value={baseField.value}
placeholder={baseField.placeholder}
className="pl-5 pr-5 py-2 border border-gray-300 rounded w-80 flex flex-col"
onChange={e => setSignInInfo(info => ({
...info,
[e.target.name]: e.target.value,
}))}
/>
...์๋ต
ํด๋ผ์ด์ธํธ ์๋ฌ ์ถ๋ ฅ
๐ ์ฐธ๊ณ ์๋ฃ
ํ๋ก ํธ์๋์ ์๋ฌํธ๋ค๋ง
์ด๋ฒ ๋ฏธ์ ์ ์งํํ๋ฉด์, ์๋ฌํธ๋ค๋ง์ ์ด๋ป๊ฒ ํด์ผ ํ ์ง ๊ณ ๋ฏผ์ด ๋ง์๋ค. ์๋ฒ์ ํต์ ๋ ๊ฐ์ด ํ๋ฉด์ ์ฌ๋ฌ ๊ฐ์ง ์๋ฌ๊ฐ ์๊ฒจ๋ฌ๊ณ , ๊ทธ์ค์๋ ๋ด๊ฐ ํต์ ํ ์ ์๋ ๋ถ๋ถ๋ ์์๋ค. ์ด๋ฒ ๊ธ์ ์์ฑ
hae-on.tistory.com
GitHub - axios/axios: Promise based HTTP client for the browser and node.js
Promise based HTTP client for the browser and node.js - axios/axios
github.com
axios post ์์ฑ
export function fetchSignIn(signInInfo, setErrors, navigate) {
axios.post(
"http://localhost:8080/api/auth/sign-in", //url
signInInfo,//body
{
headers: { "Content-Type": "application/json" } //header
},
)
...์๋ต
}
์๋ฌ ์ฒ๋ฆฌ ๋ก์ง (. catch ์ฌ์ฉ)
๋ก๊ทธ์ธ ๊ณผ์ ์์ ๋ฐ์ํ ์ ์๋ ์๋ฌ๋ ํฌ๊ฒ ์ธ ๊ฐ์ง๋ก ๋๋ ์ ์์ต๋๋ค.
์ ํจ์ฑ ๊ฒ์ฆ ์คํจ: 400 Bad Request
์กด์ฌํ์ง ์๋ ํ์: 404 Not Found
์ธ์ฆ ์คํจ : 401 Unauthorized
fetchSignIn ํจ์์์๋ .catch๋ฅผ ํตํด ์๋ฒ ์๋ต ์๋ฌ๋ฅผ ์ก๊ณ , ์ํ ์ฝ๋์ ๋ฉ์์ง๋ฅผ ๊ธฐ๋ฐ์ผ๋ก error ๊ฐ์ฒด์ ๋ด์ต๋๋ค.
๐๐ป ์ฝ๋ ๋ณด๊ธฐ
export function fetchSignIn(signInInfo, setErrors, navigate) {
api.post(
"/auth/sign-in",
signInInfo,
)
.then((res) => {
localStorage.setItem("accessToken", res.data.token.trim()),
setErrors({ loginIdError: "", passwordError: "", globalError: "" }),
navigate("/");
})
.catch(err => {
const errorMessages = err.response?.data;
const Errors = {};
//์๋ชป๋ ๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ ์
๋ ฅํ ๊ฒฝ์ฐ
if (err.response?.status === 404 || err.response?.status === 401) {
Errors.globalError = "์์ด๋ ํน์ ๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.";
}
//์ ํจ์ฑ ๊ฒ์ฆ์ด ์คํจํ ๊ฒฝ์ฐ
if (err.response?.status === 400) {
errorMessages.forEach(element => {
if (element.includes("์์ด๋")) {
Errors.loginIdError = element;
}
if (element.includes("๋น๋ฐ๋ฒํธ")) {
Errors.passwordError = element;
}
});
}
setErrors(prev => ({ ...prev, ...Errors }));
})
}
์ฝ๋ ์ค๋ช :
- .catch์์ ์๋ฒ ์๋ต(err.response)์ ํ์ธํ๊ณ ์ํ ์ฝ๋์ ๋ฐ๋ผ ์ ์ ํ ๋ฉ์์ง๋ฅผ Errors ๊ฐ์ฒด์ ๋ด์ต๋๋ค.
- 404, 401 ์๋ฌ๋ ํ๋์ ๊ณตํต ๋ฉ์์ง๋ฅผ globalError๋ก ์ค์ ํ๊ณ , 400 ์๋ฌ๋ ํ๋๋ณ ๋ฉ์์ง๋ฅผ ๊ตฌ๋ถํ์ฌ loginIdError, passwordError์ ์ ์ฅํฉ๋๋ค.
- ๋ง์ง๋ง์ setErrors๋ก ์ํ๋ฅผ ์ ๋ฐ์ดํธํ์ฌ ํ๋ฉด์ ํ์ํ ์ค๋น๋ฅผ ํฉ๋๋ค.
์๋ฌ ๋ฉ์์ง ์ปดํฌ๋ํธ
๊ฐ ํ๋์ ๊ธ๋ก๋ฒ ์๋ฌ๋ฅผ ๋ณด์ฌ์ฃผ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ญ๋๋ค.
- FieldErrorMessage: ์์ด๋, ๋น๋ฐ๋ฒํธ ํ๋ ์์ ํ์
- GlobalErrorMessage: ๋ก๊ทธ์ธ ์ ์ฒด ์คํจ ์ ์ค์์ ํ์
function FieldErrorMessage({ value }) {
return (
<p
className="font-bold text-top text-xs text-red-400 ml-28 mb-10"
> {value}</p>
);
}
function GlobalErrorMessage({ value }) {
return (
<p
className="flex font-bold text-xs text-red-400 justify-center mt-10"
> {value}</p>
);
}
์๋ต ํ ํฐ ์ ์ฅํ๊ธฐ
๐ ์ฐธ๊ณ ์๋ฃ
[React] JWT ํ ํฐ ์ ์ฅ ์์น
์๋ฒ์ API ์ฐ๋์ ํ ๋ JWT ํ ํฐ์ ๋ฐ๊ธ๋ฐ์ ์ธ์ฆ ํ๊ณ ์์ต๋๋ค.์ฐ๋ ์ค ํ ํฐ์ ์ด๋์ ์ ์ฅํด์ผ ํ ๊น? ์ ๋ํ ์๋ฌธ์ด ๋ค์์ต๋๋ค.ํ ํฐ์ ์ ์ฅํ๋ ๋ฐฉ์์ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์์ง๋ง ๋ํ์ ์ผ๋ก ๋๊ฐ
velog.io
[Spring Boot + React] ๋ฆฌํ๋ ์ ํ ํฐ๊ณผ ์ก์ธ์ค ํ ํฐ ์ ์ฅ ๋ฐฉ๋ฒ ๋ณ๊ฒฝ (HTTPOnly ์ฟ ํค, ์ ์ญ ์ํ๊ด๋ฆฌ)
์ด ๊ธ์์ ์ฌ์ฉํ ๊ธฐ์ ์คํ ๋ฒ์ ๋๋ณด๊ธฐ ์๋ฒ Spring boot 3.0.2 Spring Security 6.0.1 JJWT 0.9.1 ํด๋ผ์ด์ธํธ React 18.2.0 axios 1.6.7 ๊ธฐ์กด์๋ ๋ก๊ทธ์ธ ํ ๋ฐ๊ธ๋ฐ์ ์ก์ธ์ค ํ ํฐ์ ๋ก์ปฌ ์คํ ๋ฆฌ์ง์, ๋ฆฌํ๋ ์ ํ
cr0c0.tistory.com
ํ ํฐ(JWT)์ ์ ์ฅํ๋ ๋ฐฉ๋ฒ
- ๋ก์ปฌ ์คํ ๋ฆฌ์ง
- ์ธ์ ์คํ ๋ฆฌ์ง
- ์ฟ ํค
์ด๋ฒ ํ๋ก์ ํธ์์๋ ํ์ฌ ์ํฉ์ ๋ง์ถฐ ๋ถํ์ํ ๋ณต์ก์ฑ์ ์ค์ด๊ธฐ ์ํด ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ์ ์ฅํ๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค.
[์ด์ 1]
ํ๋ก ํธ์๋ ํ์ต์ฉ ํ๋ก์ ํธ๋ก ๊ด๋ฆฌํด์ผ ํ ํ ํฐ์ accessToken ํ๋๋ก ์ค๊ณํ์์ต๋๋ค.
โก๏ธ ๋ณต์กํ ์ธ์ฆ ํ๋ฆ์ด๋ Refresh Token ๊ด๋ฆฌ๊ฐ ํ์ํ์ง ์์์ต๋๋ค.
[์ด์ 2]
ํ ์คํธ๋ฅผ ์ํด ๋ง๋ฃ ์๊ฐ์ ์งง๊ฒ ์ค์ ํ ์์ ์ด๊ธฐ ๋๋ฌธ์ ํ์ทจ๋นํ๋๋ผ๋ ์ํ์ด ์ ์ ๊ฒ์ด๋ผ๋ ํ๋จ์ ํ์์ต๋๋ค.
โก๏ธ ์ฌ์ฉ์์ ๊ฒฝํ์ด ๋ฎ์์ง๋ฉฐ, ๋ง์ฝ ๊ณต๊ฒฉ์๊ฐ ๋ง๋ฃ ์๊ฐ์ ๋๋ฆฐ๋ค๋ฉด ๋ฐฑ์๋๊ฐ ์ด๋ฅผ ํ์ธํ ๋ฐฉ๋ฒ์ด ์์ด ์ฌ์ ํ ์ํ์ด ์กด์ฌํ๋ค๋ ์ ์ ์ธ์งํ๊ณ ์์ต๋๋ค.
๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ๊ฐ์ ์ฝ์์ ํตํด์ ๋ฐ๋ก ํ์ธ์ด ๊ฐ๋ฅํฉ๋๋ค.

์ถํ ํ์ฅ ๊ฐ๋ฅ์ฑ
ํฅํ ์ค์ ์๋น์ค๋ฅผ ์ด์ํ๊ฒ ๋๋ค๋ฉด, ๋ณด์์ ๊ฐํํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ ๋ฐฉ์์ ๊ณ ๋ คํ ๊ณํ์ ๋๋ค.
- ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ์ฟ ํค๋ฅผ ์ด์ฉํ accessToken๊ณผ Refresh ํ ํฐ ์ ์ฅ
- Redis์ Refresh Token ์ ์ฅ์ผ๋ก ์ฝ๊ฐ์ stateful ์ํ๋ฅผ ์ ์งํด ํ ํฐ ํ์ทจ ๋ฐ ์กฐ์ ์ํ ์ํ
๋ก์ปฌ ์คํ ๋ฆฌ์ง ์ฌ์ฉํ๊ธฐ (์ ์ฅ ๋ฐ ๋ถ๋ฌ์ค๊ธฐ)
๐ ์ฐธ๊ณ ์๋ฃ
LocalStorage๋ก ์ ์ฅ, ๋ถ๋ฌ์ค๊ธฐ, ์ญ์ (JS, React)
ํน๋ณํ ์๋ฒ๋จ์ ์ฌ์ฉํ์ง ์์ ์์ ์ด๊ณ , ์ค์ํ ๋ด์ฉ์ ๋ด๊ณ ์์ง ์๋ค๋ฉด ๋ก์ปฌ์คํ ๋ฆฌ์ง๋ฅผ ์ด์ฉํ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ ์ค ํ๋์ด๋ค. ๋ก์ปฌ ์คํ ๋ฆฌ์ง๋ฅผ ์ด๋ป๊ฒ ์ด์ฉํ์ฌ ์ ์ฅ, ๋ถ๋ฌ์ค๊ธฐ, ์ญ์ ๋ฅผ ํ
patrick-f.tistory.com
๋ก๊ทธ์ธ ์ localStorage.setItem( "์ ์ฅํ๊ณ ์ถ์ ์ด๋ฆ", ๋ฃ๊ณ ์ถ์ ๋ฐ์ดํฐ)์ ํตํด ํ ํฐ ์ ์ฅ
import axios from "axios";
export function fetchSignIn(signInInfo) {
axios.post(
"http://localhost:8080/api/auth/sign-in",
signInInfo,
{
headers: { "Content-Type": "application/json" }
},
)
.then((res) =>
localStorage.setItem("accessToken", res.data.token.trim())
)
.catch(err => console.log(err));
}
๐๐ป trim์ ์ฌ์ฉํ ์ด์
์ฌ์ฉ ์ ๋ฐฑ์๋์์ ์๋์ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์์ต๋๋ค.

์ฝ์๋ก ํ ํฐ ๊ฐ์ ํ์ธํด ๋ณด๋, ๋ฐฑ์๋์ ํ๋ก ํธ ๋ชจ๋์์ ํ ํฐ์ด ์ฌ๋ฌ ์ค๋ก ์ถ๋ ฅ๋๋ ๊ฒ์ ํ์ธํ ์ ์์์ต๋๋ค.
- ์๋ง ์ ์ก ๊ณผ์ ์์ ํ ํฐ์ด ๋๋ฌด ๊ธธ์ด ์ค๋ฐ๊ฟ์ด ๋ฐ์ํ ๊ฒ์ผ๋ก ์ถ์ธก๋ฉ๋๋ค.
- ์ด๋ก ์ธํด JWT๊ฐ ์ ํจํ์ง ์์ ๋ฌธ์์ด๋ก ์ธ์๋์ด ์์ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฒ ๊ฐ์ต๋๋ค.


๋ค๋ฅธ api์์๋ localStorage.getItem( "์ ์ฅํ๋ ์ด๋ฆ")์ ํตํด accessToken์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
ํ์ด์ง ์ด๋ํ๊ธฐ ( useNavigate)
๋ก๊ทธ์ธ ์ฑ๊ณต ์(๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญ): ๋ฉ์ธ ํ์ด์ง๋ก ์ด๋
ํ์๊ฐ์ ๋ฒํผ ํด๋ฆญ ์ : ํ์๊ฐ์ ํ์ด์ง ์ด๋
๋ก๊ทธ์ธ ๋ฒํผ์ ๋๋ ์ ๋ ์๋ฌ ์์ด ์ ์์ ์ผ๋ก ์ฒ๋ฆฌ๋๋ค๋ฉด ๋ฉ์ธ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.
๐กํ ์ ์์น
useNavigate์ ๊ฐ์ ํ ์ React๊ฐ ์ํ๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌ๋ถํ ์ ์๋๋ก ์ปดํฌ๋ํธ ์ต์์์ ์์น์์ผ์ผ ํฉ๋๋ค.
๋ฐ๋ผ์, ์๋จ์์ useNavigate๋ฅผ ์ ์ธํ ๋ค, ํด๋น ๊ฐ์ fetch ํจ์์ props๋ก ์ ๋ฌํ์ฌ ์ฌ์ฉํ๋๋ก ๊ตฌํํ์ต๋๋ค.
โก๏ธ React๋ ํ ์ ํธ์ถ ์์๋ฅผ ๊ธฐ์ค์ผ๋ก ์ํ๋ฅผ ๊ตฌ๋ถํฉ๋๋ค.
SignIn ์ปดํฌ๋ํธ
function SignInPage() {
const [showPassword, setShowPassword] = useState(false);
const [signInInfo, setSignInInfo] = useState({ loginId: "", password: "" });
const [errors, setErrors] = useState({ loginIdError: "", passwordError: "", globalError: "" });
const navigate = useNavigate() // navigate ํ ๋น
const fields = [
{ name: "loginId", label: "์์ด๋", type: "text", value: signInInfo.loginId, placeholder: "์์ด๋๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.", error: errors.loginIdError },
{ name: "password", label: "๋น๋ฐ๋ฒํธ", type: "password",value: signInInfo.password, placeholder: "๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.", error: errors.passwordError }
];
//๋ก๊ทธ์ธ ํผ ์ ์ถ
function handleSignInSubmit(e, signInInfo, setErrors, navigate) {
e.preventDefault();
setErrors({ loginIdError: "", passwordError: "" });
fetchSignIn(signInInfo, setErrors, navigate) //api ํธ์ถ ํจ์
}
fetchSignIn ํจ์
export function fetchSignIn(signInInfo, setErrors, navigate) {
api.post(
"/auth/sign-in",
signInInfo,
)
.then((res) => {
localStorage.setItem("accessToken", res.data.token.trim()),
setErrors({ loginIdError: "", passwordError: "", globalError: "" }),
navigate("/"); //์ด๋
})
์ด๋ฒ ๊ณผ์ ์์ ๊ฐ์ฅ ๊ณ ๋ฏผํ๋ ๋ถ๋ถ์ ํ ํฐ ์ ์ฅ ๋ฐฉ๋ฒ๊ณผ ๋ฐฑ์๋์ ํด๋ผ์ด์ธํธ ๊ฐ์ ์๋ฌ ์ฒ๋ฆฌ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
์์ง๋ ์๋ฌ ์ฒ๋ฆฌ ๋ถ๋ถ์ ์์ ํ ๊ฐ์ด ์กํ์ง ์์๊ณ , ๋ง์ฝ ํ์ฌ ํฌ์คํฐ์์ ๋ค๋ฃฌ ๊ฒ๋ณด๋ค ํจ์ฌ ๋ค์ํ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค๋ฉด, ์ง๊ธ๊ณผ ๊ฐ์ ๋ฐฉ์์ ํ๋ ์ฝ๋ฉ์ด ๊ณ์ ๋์ด๋๋ ๋ฑ ํ๊ณ๊ฐ ์กด์ฌํ ๊ฒ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
๋ค์ ํฌ์คํฐ์์๋ ๋ ๋ง์ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ ํ์๊ฐ์ ํ์ด์ง ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ฅผ ๋ค๋ฃจ๋ฉด์, ๋ค์ ํ๋ฒ ์๋ฌ ์ฒ๋ฆฌ์ ๋ํ ๋ด์ฉ์ ์ ๋ฆฌํด์ผ ํ ๊ฒ ๊ฐ์ต๋๋ค.
'React' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| useInfiniteQuery โ useInView๋ก ๋ฌดํ ์คํฌ๋กค ๊ตฌํํ๊ธฐ (0) | 2025.08.26 |
|---|---|
| [๋ฆฌ์กํธ] ๋ด๋น๊ฒ์ด์ ๋ฐ ๊ฒ์ ๊ตฌํ (feat. <form> ํ๊ทธ , ํ์ํ ์๊ฐ์?) (0) | 2025.08.19 |
| [๋ฆฌ์กํธ] ๋ฐฑ์๋ API ํธ์ถ (feat. Get ์์ฒญ) (0) | 2025.08.11 |
| [๋ฆฌ์กํธ] fetch์ axios ์ฐจ์ด (2) | 2025.08.11 |
| [๋ฆฌ์กํธ] ๋ค๋น๊ฒ์ด์ ๋ฐ ์ ์ฉํ๊ธฐ (ํ๋ฉด ๊ตฌ์ฑ ๊ตฌํ) (4) | 2025.08.09 |