const [error, setError] = useState();
const [posts, setPosts] = useState();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
async function loadPosts() {
setIsLoading(true);
try {
const posts = await getPosts();
setPosts(posts);
} catch (err) {
setError(err.message);
}
setIsLoading(false);
}
loadPosts();
}, []);
그래서 우리는 react-router-dom 6.4 이상의 버젼을 눈여겨 볼 필요가 있다.
import 컴포넌트, {loader as 로더함수} from "path"
<Route index element={<컴포넌트 />} loader={로더함수}/>
const 컴포넌트함수 => () => {
const loaderData = useLoaderData();
return(
<>
<컴포넌트 프롭={loaderData}/>
</>
)
}
export default 컴포넌트함수;
export function 로더함수({params}){
return 패치함수(parms.키값);
};
로더함수는 컴포넌트 함수에서 사용할 수 있는 데이터를 반환할 것이다. 그렇게 코드를 짰으니까
이때 배열이나 객체일 수도 있고 텍스트나 숫자일 수도 있다
어떤 형태이든 위 함수에서 사용하는 데이터를 리졸브(resolve)하는 프로미스여야 한다.
이때 프로미스객체는 가져오기에 실패하면 사용자 지정 오류 객체를 발생시키고 성공할 경우에는 JSON 데이터를 리졸브하는 프로미스를 응답의 일부로 반환한다.
객체는 페이지 전환 관련 데이터를 포함한 request 객체이거나 params 객체일 수 있다.
params 객체 뒤에 URL의 path값을 붙이면 URL을 통해 게시물 id에 액세스하게 만들 수 있다.
이때 우리는 로딩 상태는 걱정할 필요 없다 백그라운드에서 데이터를 가져온 후에만 페이지를 로드하니까
const router = createBrowserRouter([
{path:"" ,element:<컴포넌트/>},
{path:"" ,element:<컴포넌트/>},
{path:"" ,element:<컴포넌트/>},
]);
const router = createBrowserRouter(createRoutesFromElements(
<Route path={"/"} element={<RootLayout/>}>
<Route index element={<WelcomePage />} />
<Route path="/blog" element={<BlogLayout />}>
<Route index element={<BlogPostsPage />} loader={blogPostsLoader}/>
<Route path=":id" element={<PostDetailPage />} />
</Route>
<Route path="/blog/new" element={<NewPostPage />} />
</Route>
));
"/"
으로 초기화시켜주면 좋다.index
props는 startwith 컴포넌트를 만났을 때 내부 자식 컴포넌트 중 index props을 갖고있다는 해당 컴포넌트를 기본적으로 먼저 보여준다.<Outlet\>
컴포넌트를 넣어 잡아주어야한다는 점이다.<Route path={"/"} element={<RootLayout/>} errorElement={<ErrorPage/>}>
<Route index element={<WelcomePage />} />
<Route path="/blog" element={<BlogLayout />}>
<Route index element={<BlogPostsPage />} loader={blogPostsLoader}/>
<Route path=":id" element={<PostDetailPage/>} loader={blogPostLoader}/>
</Route>
<Route path="/blog/new" element={<NewPostPage />} />
</Route>
throw { message: 'Failed to fetch post.', status: 500 };
const error = useRouteError();
return (<p>error.messages</p>)
async function submitHandler(event) {
event.preventDefault();
setIsSubmitting(true);
try {
const formData = new FormData(event.target);
const post = {
title: formData.get('title'),
body: formData.get('post-text'),
};
await savePost(post);
navigate('/');
} catch (err) {
setError(err);
}
setIsSubmitting(false);
}
<Route path="/blog/new" element={<NewPostPage />} action={newPostAction}/>
export async function action({request}) { // 1.
const formData = await request.formData(); // 2.
const post = { // 3.
title: formData.get("title"),
body: formData.get("post-text")
};
try {
savePost(post); // 4.
} catch (err) {
if (err.status === 422) {
return err; // 6.
}
throw err;
}
return redirect("/blog"); // 5.
};
위 사진의 Form이 제출될 때마다 이 action 함수가 실행되며 Form data를 포함하는 객체가 자동으로 생성된다
여기서도 params 객체나 request 객체를 얻을 수도 있는데 요청 객체가 더 중요하다.
왜냐하면 자동으로 생성된 이 요청 객체는 아직 브라우저를 떠나지 않았기 때문이다.
name
속성과 비동기를 이용하면 된다.이때 또 중요한 것은 request 객체에서 formData 메서드를 실행하면 프로미스가 반환되는데 이를 대기(Await)해야한다.
여기서 title과 body는 반드시 작성요소는 아니고 단순히 savePost 매서드이 유효성 검사 코드를 그렇게 짰기 때문이다.
savePost도 프로미스를 반환하므로 await을 붙여 주고 오류가 발생할 수 있으니 try-catch 블록으로 감싸 준다.
전에는 React Router가 기본적으로 제공하는 navigate 함수를 호출했었다.
하지만 redirect 함수도 React Router에서 가져온 또 다른 함수로 다른 페이지로 이동하도록 브라우저를 트리거하는 응답을 생성한다.
여기서 throw err
가 아닌 return err
를 해줌으로써 다시 상위 컴포넌트로 넘어가는 것이 아닌 현재 컴포넌트에 머물면서 useActionData 훅을 사용해 반환된 데이터에 액세스할 수 있다.
그래서 작은 경고 문구같은 것을 통한 유효성 검사를 구현할 수 있다.
function DeferredBlogPostsPage() {
const loaderData = useLoaderData();
return (
<>
<h1>Our Blog Posts</h1>
<Suspense fallback={<p>Loading...</p>}> // 3.
<Await
resolve={loaderData.posts} // 2.
errorElement={<p>Error loading blog posts.</p>}
>
{(loadedPosts) => <Posts blogPosts={loadedPosts} />} // 4.
</Await>
</Suspense>
</>
);
}
export default DeferredBlogPostsPage;
export async function loader() {
return defer({posts: getSlowPosts(), 키:함수(), ...}); //1 .
}
function signupForNewsletterHandler(event) {
event.preventDefault();
const enteredEmail = emailEl.current.value;
// could validate input here
fetcher.submit(
// better: use fetcher.Form instead
{ email: enteredEmail },
{ method: 'post', action: '/newsletter' }
);
}
return (
<section className={classes.newsletter}>
<h2>Sign up for our weekly newsletter</h2>
<form onSubmit={signupForNewsletterHandler}>
<input
ref={emailEl}
id="email"
type="email"
placeholder="Your email"
aria-label="Your email address."
/>
<button>Sign Up</button>
</form>
</section>
);