์ด์ด์ ์ ์ด์ด์ ๋ฆฌ์กํธ ํ์ต์ ํ์๋ค!
๋จ๊ธฐํน๊ฐ ์ฝ์ค๋ก ๋ฃ๋ค๋ณด๋, ํ์ต์ ์์ด ์ข ๋ง์๋ค!
์กฐ๊ฑด๋ถ ๋ ๋๋ง
๋ค์ํ ๋ฐฉ์์ผ๋ก ์กฐ๊ฑด์ ๋ฐ๋ผ ์ปดํฌ๋ํธ ๋ ๋๋ง์ ํ ์ ์๋๋ฐ, ์ง๊ธ๊ณผ ๊ฐ์ด true ๊ฒฝ์ฐ์๋ง ๋ ๋๋งํ ๋, &&์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค!
3ํญ ์ฐ์ฐ์
{ModalIsVisible ? (
<Modal onClose={hideModalHandler}>
<NewPost
onBodyChange={bodyChangeHandler}
onAuthorChange={authorChangeHandler}
/>
</Modal>
) : null}
false์ผ๋ null
&&์ฐ์ฐ์
{ModalIsVisible && (
<Modal onClose={hideModalHandler}>
<NewPost
onBodyChange={bodyChangeHandler}
onAuthorChange={authorChangeHandler}
/>
</Modal>
)}
๋ณ์ ์ฌ์ฉ
let modalContent;
if (ModalIsVisible) {
modalContent = (
<Modal onClose={hideModalHandler}>
<NewPost
onBodyChange={bodyChangeHandler}
onAuthorChange={authorChangeHandler}
/>
</Modal>
);
}
return (
<>
{modalContent}
<ul className={classes.posts}>
<Post author={enteredAuthor} body={enteredBody} />
<Post author="Manuel" body="Check out the full course!" />
</ul>
</>
);
}
๋ณ์๋ฅผ ์ฌ์ฉํด์ ๊ด๋ฆฌํ ๋, ์ข ๋ ํจ์จ์ ์ธ ๊ฒฝ์ฐ๊ฐ ์์ง๋ง ์ง๊ธ๊ณผ ๊ฐ์ด ๋จ์ํ ๊ฒฝ์ฐ์๋ ์ง์ํ๋ ๊ฒ์ด ์ข๋ค.
useState์ ์ํ ์ ๋ฐ์ดํธ ํจ์
const [posts, setPosts] = useState([]);
function addPostHandler(postData) {
setPosts([postData, ...posts]);
}
setCount((prevCount) => prevCount + 1);
ํ์ฌ ์ํ์ ์ค๋ ์ท์ ๋งค๊ฐ๋ณ์๋ก ๋ฐ๋๋ค!
๊ทธ๋ฆฌ๊ณ , return ๊ฐ์ผ๋ก ์๋ก์ด ์ํ๋ฅผ ๋ฐํํด์ผํ๋ค.
๋ฆฌ์กํธ๋ ๋ด๋ถ์์ ์ํ ์ ๋ฐ์ดํธ ํจ์๋ฅผ ๋ฐ๋ก ์คํํ์ง ์๊ณ , ์์ฝํด๋๋ ๊ฒ์ด๋ผ๊ณ ํ๋ค!
๊ทธ๋์ ์ฌ๋ฌ ์ ๋ฐ์ดํธ๊ฐ ์์ผ์ ์๋ชป๋ ์ํ๋ฅผ ๊ฐฑ์ ํ ์ ์์ง๋ง, ๋ฆฌ์กํธ๊ฐ ์ต์ ๋ฒ์ ์ ์ ํจ์ํ๋ฅผ ๊ฐ์ ธ์ ์ ๋๋ก ๊ฐฑ์ ํ๋๋ก ํด์ค๋ค.
SPA & Router
SPA
React๋ฅผ ์ฌ์ฉํ ๋จ์ผ ํ์ด์ง ์ ํ๋ฆฌ์ผ์ด์ (Single Page Application, SPA)์ ์ ํต์ ์ธ ๋ค์ค ํ์ด์ง ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ๋ ๋ค๋ฅด๊ฒ, SPA๋ ํ๋์ HTML ํ์ด์ง๋ฅผ ๋ก๋ํ๊ณ , ์ฌ์ฉ์๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ์ฉํ๋ ๋์ ํ์ด์ง๋ฅผ ๋ค์ ๋ก๋ํ์ง ์๊ณ ํ์ํ ๋ถ๋ถ๋ง ๋์ ์ผ๋ก ์ ๋ฐ์ดํธํฉ๋๋ค.
Router
๋ฆฌ์กํธ SPA์์๋ ๊ฒฝ๋ก์ ๋ฐ๋ผ ๋ค๋ฅธ ๋ทฐ๋ฅผ ๋์ด๋ค. ์ด๋ ๋ผ์ฐํ ์ ๊ฐ๋ ์ ์ฌ์ฉํ๋ค.
๋ผ์ฐํ ์ ๋ค๋ฅธ ์ฃผ์์ ๋ฐ๋ผ ๊ฐ ๋ค๋ฅธ ๋ทฐ๋ฅผ ๋ณด์ฌ์ฃผ๊ณ , ๊ฒฝ๋ก์ ๋ฐ๋ผ ๋ณํํด์ค๋ค.
์ฆ, SPA๋ผ๊ณ ํ์ง๋ง ๊ฐ ๋ค๋ฅธ ๋ทฐ๋ฅผ ๊ฐ์ง๊ณ ์๋๋ฐ , ๊ฐ ๋ค๋ฅธ ์ฃผ์๋ฅผ ๋ถ์ฌํ๊ณ ๊ฒฝ๋ก ๋ณ๊ฒฝ์ผ๋ก์จ ๋ค๋ฅธ ๋ทฐ๋ฅผ ๋ณด์ฌ์ฃผ๋๊ฒ!
const router=createBrowerRouter([
{path:'/',
element:<RootLayout/>,
children:[
{path:'/',
element:<Posts/>,
children:[
{path:'/create-post',element:<NewPost/>}
]}
]}
])
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RouterProvider router={router}/>
</React.StrictMode>
);
createBrowerRouter๋ก ๋ง๋ ๋ผ์ฐํ ์ RouterProver์ ์์ฑ์ ๋ฃ์ด render์์ ๋ฃ์ด์ค๋ค!
Fetch
fetch() ํจ์๋ ์น API๋ก, ๋คํธ์ํฌ ๋ฆฌ์์ค๋ฅผ ๋น๋๊ธฐ์ ์ผ๋ก ์์ฒญํ๊ณ ์๋ต์ ๋ค๋ฃจ๋ ๋ฐ ์ฌ์ฉ๋๋ฉฐ, AJAX ์์ฒญ์ ์ฝ๊ฒ ๋ง๋ค ์ ์์ต๋๋ค. ์ด ํจ์๋ Promise๋ฅผ ๋ฐํํ๋ฉฐ, ๋คํธ์ํฌ ์์ฒญ์ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋๋ก ๋์์ค๋๋ค.
fetch(url, options)
.then(response => {
// ์๋ต์ ์ฒ๋ฆฌ
return response.json(); // ๋๋ response.text(), response.blob() ๋ฑ์ ์ฌ์ฉํ ์ ์์
})
.then(data => {
// ์ฒ๋ฆฌ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ
})
.catch(error => {
// ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌ
});
(๋งค๊ฐ๋ณ์)
- url: ์์ฒญ์ ๋ณด๋ผ URL์ ๋๋ค.
- options: ์ค์ ๊ฐ์ฒด๋ก, ์์ฒญ์ ๋ํ ์ฌ๋ฌ ์ต์
์ ์ค์ ํ ์ ์์ต๋๋ค. ๋ฉ์๋, ํค๋, ๋ฐ๋ ๋ฑ์ด ์ฌ๊ธฐ์ ํฌํจ๋ฉ๋๋ค.
- method: HTTP ๋ฉ์๋๋ฅผ ์ง์ ํฉ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ "GET"์ ๋๋ค.
- headers: ์์ฒญ ํค๋๋ฅผ ์ง์ ํ๋ ๊ฐ์ฒด
- body: ์์ฒญ์ ๋ฐ๋ ์ง์ . (๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ๋๋ JSON.stringify()๋ฅผ ์ฌ์ฉํด JSON๋ก ํฌ๋งทํด์ ๋ณด๋ธ๋ค)
- mode: ์์ฒญ ๋ชจ๋ ์ง์ . ("cors", "no-cors", "same-origin" ๋ฑ์ด ๊ฐ๋ฅ)
POST: ์๋ฒ๋ก ์์ฒญ ๋ณด๋ด๊ธฐ
function addPostHandler(postData) {
fetch("http://localhost:8080/posts", {
method: "POST",
body: JSON.stringify(postData), //json์ผ๋ก ๋ณํํด์ ์์ฒญ
headers: {
"Content-Type": "application/json",
},
});
setPosts([postData, ...posts]);
}
๋ฐฑ์๋์์ ์ ๋ณด๋ฅผ ๋ฐ์ ํ์ผ์ ์ ์ฅํ๋ค.
GET: ์๋ฒ๋ก๋ถํฐ data ๋ฐ์์ค๊ธฐ
Promise ๊ฐ์ฒด
Promise๋ ๋น๋๊ธฐ ์์ ์ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๊ฐ์ฒด๋ก, ๋น๋๊ธฐ ์์ ์ ์ฃผ๋ก ๋คํธ์ํฌ ์์ฒญ, ํ์ผ ์ฝ๊ธฐ, ํ์ด๋จธ ๋ฑ๊ณผ ๊ฐ์ด ์๊ฐ์ด ๊ฑธ๋ฆฌ๋ ์์ ์ ์๋ฏธํ๋ค.
Promise๋ ๋น๋๊ธฐ ์์ ์ ์ฑ๊ณต ๋๋ ์คํจ์ ๊ฐ์ ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ๋ํ๋ด๋๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
- ๋๊ธฐ(pending): ์ด๊ธฐ ์ํ๋ก, ํ๋ก๋ฏธ์ค๊ฐ ์ฑ๊ณตํ๊ฑฐ๋ ์คํจํ ๋๊น์ง์ ์ํ์ ๋๋ค.
- ์ดํ(fulfilled): ๋น๋๊ธฐ ์์ ์ด ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋์ด ํ๋ก๋ฏธ์ค๊ฐ ๊ฒฐ๊ณผ๊ฐ์ ๋ฐํํ ์ํ์ ๋๋ค.
- ๊ฑฐ๋ถ(rejected): ๋น๋๊ธฐ ์์ ์ด ์คํจํ๊ฑฐ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ํ์ ๋๋ค.
const fetchData = () => {
return new Promise((resolve, reject) => {
// ๋น๋๊ธฐ ์์
์ํ (์: ๋คํธ์ํฌ ์์ฒญ)
const data = /* ... */;
// ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋ ๊ฒฝ์ฐ
if (data) {
resolve(data);
} else {
// ์คํจํ ๊ฒฝ์ฐ
reject('Error fetching data');
}
});
};
// ํ๋ก๋ฏธ์ค ์ฌ์ฉ
fetchData()
.then(result => {
console.log('Success:', result);
})
.catch(error => {
console.error('Error:', error);
});
resolve์ reject๋ ํ๋ก๋ฏธ์ค์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ํจ์๋ก,
์ฑ๊ณต ์ resolve๋ฅผ ํธ์ถํ์ฌ ํ๋ก๋ฏธ์ค๋ฅผ ์ดํ ์ํ๋ก ๋ง๋ค๊ณ , ์คํจ ์ reject๋ฅผ ํธ์ถํ์ฌ ํ๋ก๋ฏธ์ค๋ฅผ ๊ฑฐ๋ถ ์ํ๋ก ๋ง๋ ๋ค.
useEffect
React Hook ์ค ํ๋๋ก, ํจ์ํ ์ปดํฌ๋ํธ์์ ๋ถ์ ํจ๊ณผ(side effects)๋ฅผ ์ํํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ค.
๋ถ์ ํจ๊ณผ๋ ์ปดํฌ๋ํธ ์ธ๋ถ์ ์ํฅ์ ๋ฏธ์น๋ ์์ ์ ๋งํ๋ฉฐ, ์ฃผ๋ก ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ, ๊ตฌ๋ ์ค์ , ์๋์ผ๋ก DOM ์กฐ์ ๋ฑ์ด ํด๋น๋๋ค.
fetch("http://localhost:8080/posts")
.then((response) => response.json())
.then((data) => setPosts(data.posts));
//server return ๊ฐ
//res.json({ posts: storedPosts });
๊ฐ์์์ ์ด์ ๊ฐ์ ์์๋ฅผ ๋ค๋ฉด์, useEffect๋ฅผ ์ฌ์ฉํด์ผ ํ๋ ์ด์ ๋ฅผ ์ค๋ช ํด์คฌ๋ค.
ํญ์ useEffect๋ฅผ ์ ํํ ์ธ์ ์ฌ์ฉํด์ผํ๋์ง ์ ํํ ์ ๋ชจ๋ฅด๋ ์ํ์๋๋ฐ ์ด์ฐธ์ ์๊ฒ๋์๋ค!
์์ ๊ฐ์ ์ฝ๋์์ fetch๋ฅผ ํตํด ์๋ฒ์์ ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ setPosts(์ํ ์ ๋ฐ์ดํธ ํจ์)๋ก ์ํ๋ฅผ ์ ๋ฐ์ดํธ ํ๊ณ ์ ํ๋ค.
๐ค ์ ์ด๋ ๊ฒ ์ฌ์ฉํ๋ฉด ์๋ ๊น??
๐ก
์ํ ์ ๋ฐ์ดํธ ํจ์๋ฅผ ํตํด ์๋ก์ด ์ํ๋ก ๊ฐฑ์ → ์ปดํฌ๋ํธ ํจ์๊ฐ ๋ค์ ์คํ → (ํจ์๊ฐ ๋ค์ ์คํ๋์ด) fecth ์์ฒญ ๋ค์ ๋ณด๋ด๊ณ , ๋ฐ์ดํฐ ๋ฐ๊ณ , ์ํ ๊ฐฑ์ ํ๊ณ ...? → ๊ฐ์ ๋์ ๋ฌดํ ๋ฐ๋ณต...!!!
useEffect๋ฅผ ์ฌ์ฉํ๋ค๋ฉด?
useEffect๊ฐ ์ปดํฌ๋ํธ ํจ์์์ sideEffect๋ฅผ ์ ์ ํ๊ฒ ์ผ์ผํค๊ธฐ ๋๋ฌธ์, ์ํํ๋ ค๋ ๋์์ด JSX์ ์ํฅ์ ๋ฏธ์น์ง ์๊ณ , ๋์ค์ ๊ฐ์ ์ ์ผ๋ก ์ํฅ์ ์ฃผ๊ฑฐ๋ UI์ ๋ค๋ฅธ์์ ์ ํ๋ ๊ฒฝ์ฐ์ ์ฌ์ฉํ๋ค๊ณ ํ๋ค. ๊ทธ๋์ ๋ฌดํ๋ฃจํ ์์ด ์คํํ ์ ์๋ค๊ณ ํ๋ค.
useEffect(() => {
// ๋ถ์ ํจ๊ณผ๋ฅผ ์ํํ๋ ์ฝ๋
// ...
// ๋ถ์ ํจ๊ณผ๋ฅผ ์ ๋ฆฌํ๋(clean-up) ์ฝ๋ (์ต์
)
return () => {
// ์ ๋ฆฌ ์ฝ๋
// ...
};
}, [dependencies]);
(๋งค๊ฐ๋ณ์)
- ์ฝ๋ฐฑ ํจ์: useEffect์ ์ฒซ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ก ์ ๋ฌ๋๋ ํจ์๋ ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋ ๋๋ง๋ค ์คํ๋๋ค. ํจ์๊ฐ ์คํ๋์ด์ผ ํ๋จ๋ ๋, ์์์ ์คํ๋๋ค!
- ์์กด์ฑ ๋ฐฐ์ด (dependencies): ๋ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ก ์ ๋ฌ๋๋ ๋ฐฐ์ด์ useEffect๊ฐ ์์กดํ๋ ๊ฐ๋ค์ ๋ชฉ๋ก์ด๋ค. ์ด ๋ฐฐ์ด์ ํฌํจ๋ ๊ฐ๋ค ์ค ํ๋๋ผ๋ ๋ณ๊ฒฝ๋๋ฉด useEffect๊ฐ ๋ค์ ์คํ๋ฉ๋๋ค. (๋น ๋ฐฐ์ด[]์ , useEffect๋ ์ปดํฌ๋ํธ๊ฐ ์ฒ์ ๋ง์ดํธ๋ ๋ ํ ๋ฒ๋ง ์คํ)
์ปดํฌ๋ํธ ํจ์์ ํจ๊ป ์คํ๋์ด์ผํ ๋ ์ฌ์ฉ! - ์ ๋ฆฌ(clean-up) ํจ์: useEffect ์์์ ๋ฐํ๋ ํจ์๋ ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋๊ฑฐ๋ ์์กด์ฑ ๋ฐฐ์ด์ ์๋ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋ ์คํ๋ฉ๋๋ค. ์ด๋ฅผ ์ด์ฉํ์ฌ ๋ถ์ ํจ๊ณผ์์ ์์ฑ๋ ๋ฆฌ์์ค๋ฅผ ์ ๋ฆฌํ๊ฑฐ๋ ๊ตฌ๋ ์ ํด์ ํ ์ ์์ต๋๋ค.
useNavigate
<a>์ฒ๋ผ ํด๋ฆญ์ url์ ์ด๋ํ๋๊ฒ์ Link ํ๊ทธ๋ฅผ ์ฌ์ฉํ์ง๋ง,
์ ์์๊ฐ์ด ๋ชจ๋ฌ<div> ํด๋ฆญ์ ์ด๋ฒคํธํธ๋ค๋ฌ ํจ์์ useNavigate hook์ ์ฌ์ฉํด ํ์ด์ง๋ฅผ ์ด๋ํ๋ ๋ฐฉ๋ฒ์ด ์๋ค
loader(): ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
loader ํ๋กํผํฐ๋ ํจ์๋ฅผ ๊ฐ์ผ๋ก ๋ฐ๊ณ , react-router๋ ํด๋น ๋ผ์ฐํฐ๊ฐ ํ์ฑํ ๋ ๋(๋ ๋๋ง)๋ง๋ค loader์ ์๋ ํจ์๋ฅผ ์คํํ๋ค.
-> ๋ผ์ฐํธ ์ปดํฌ๋ํธ(ํ์ํฌํจ)์๊ฒ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ก๋ํด๋ ์ ์๋ค.
โ๏ธ ๋น๋๊ธฐํจ์๋ฅผ ๋ฃ์ด๋๋์ง๋ง, promise๋ฅผ ๋ฆฌํดํ๋ค๋ฉด promise๊ฐ์ ๋ฐ์๋๊น์ง ๊ธฐ๋ค๋ ธ๋ค๊ฐ ํด๋น ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๋ค.
export async function loader() {
const response = await fetch("http://localhost:8000/posts");
const resData = await response.json();
return resData.posts;
}
(๋ณดํต ํด๋น component์ loaderํจ์๋ฅผ ์์ฑ)
์ปดํฌ๋ํธ ๋ฐ๊นฅ์์ ์คํ๋๊ธฐ ๋๋ฌธ์, ์ปดํฌ๋ํธ ์ํ๋ฅผ ๋ฐ๊พธ์ง ์์ useEffect๋ฅผ ์ฌ์ฉํ์ง ์์๋๋๋ค!
ํ๋ฉด์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋ฉด , ๊ทธ ๋ฐ์ดํฐ๋ฅผ ํ์ฌ ๋ผ์ฐํธ์ ๋ ๋๋ง๋ ์์๊ฐ ๋ฐ๋๋ค.
import Posts,{loader as postsLoader} from './routes/Posts'
import NewPost,{action as newPostAction} from './routes/NewPost'
import PostDetails,{action as postDetailLoader} from './routes/PostDetails'
const router=createBrowerRouter([
{path:'/',
element:<RootLayout/>,
children:[
{path:'/',
element:<Posts/>,
loader: postsLoader
children:[
{path:'/create-post',element:<NewPost/> action:newPostAction }
{path:'/:id',element:<PostDetails/> loader:postDetailLoader}
]}
]}
])
loader()์์ ๋ฐํํ ๋ฐ์ดํฐ๋ Posts ์ปดํฌ๋ํธ& ์ค์ฒฉ๋ ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ ์ ์๊ฒ๋๋ค.
loader์ ๊ฐ์ฒด request, params๋ฅผ ์ฌ์ฉํ๋ฉด ๋ผ์ฐํธ์ id์ ๊ฐ์ ธ์ฌ์์๋ฐ
useLoaderData
์ปดํฌ๋ํธ ํจ์ ๋ด๋ถ์์ loader()์์ ๋ฐํํ ๋ฐ์ดํฐ๋ฅผ ๊ฐธ์ ธ์ฌ ์ ์๋ค.
๐๐ป ๋ด๋ถ PostList์ (useEffect์)fetch๋ถ๋ถ๊ณผ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ์ํ state, loading์ํ ๊น์ง ๋ชจ๋ ์ง์๋ฒ๋ฆด์ ์๋ค!
action(): ๋ฐ์ดํฐ ๋ณด๋ด๊ธฐ
ํด๋น ๋ผ์ฐํฐ์์ form์ด ์ ์ก๋ ๋ ์คํ๋๋ค.
โ๏ธ react-router-dom์ Form ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ฉด , form ์ ์ก์ ์ฒ๋ฆฌํด ๋ธ๋ผ์ฐ์ ๊ฐ ์์ฒญ์ ์ ์กํ์ง ๋ชปํ๊ฒ ๋ง์์ฃผ๊ณ , ๋ชจ๋ ์ ๋ ฅ ๋ฐ์ดํฐ๋ฅผ ์์งํด ํด๋น ๋ฐ์ดํฐ์ ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํด์ค๋ค.
form์์ name ์์ฑ์ ์ ์ํ ์ด๋ฆ์ ์ฌ์ฉํด ๋ฐ์ดํฐ๋ฅผ ์ถ์ถํ๋ค.
import NewPost,{action as newPostAction} from './routes/NewPost'
const router=createBrowerRouter([
{path:'/',
element:<RootLayout/>,
children:[
{path:'/',
element:<Posts/>,
loader: postsLoader
children:[
{path:'/create-post',element:<NewPost/>,action:newPostAction}
]}
]}
])
๐๐ป ๋๋ถ์, useState, eventhandler ํจ์๋ค๋ ๋ชจ๋ ์ญ์ ํ ์์๋ค.
redirect()
action, loaderํจ์ ์์์ ํธ์ถํด, ์ด ํจ์์ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ ์ ์๋ค. (์๋ต ๊ฐ์ฒด๊ฐ ๋ง๋ค์ด์ง)
-> ์ฐ๋ฆฌ๊ฐ ์ด๋ํ๊ณ ์ ํ๋ ๊ฒฝ๋ก๋ก ์ด๋์์ผ์ค๋ค.