클라이언트 컴포넌트에서 서버 액션을 호출하여 로딩 상태 설정, 에러 핸들링 등의 기능을 구현할 수 있다.
서버에서 데이터가 응답되어 화면에 렌더링되기까지, 시간적 지연 동안 사용자에게 '무언가'를 보여주지 않으면 답답한 느낌을 받을 수 밖에 없다.
특히나 로딩 중인 상태에서는 추가적인 액션을 막아두지 않으면, 작업 하나가 미처 종료되기 전에 중복된 요청이 발생할 수도 있다.
"use client";
import style from "./review-editor.module.css";
import { createReviewAction } from "@/actions/create-review.action";
import { useActionState, useEffect } from "react";
export default function ReviewEditor({ bookId }: { bookId: string }) {
const [state, formAction, isPending] = useActionState(
createReviewAction,
null
);
useEffect(() => {
if (state && !state.status) {
alert(state.error);
}
}, [state]);
return (
<section>
<form className={style.form_container} action={formAction}>
<input name="bookId" value={bookId} hidden />
<textarea
disabled={isPending}
required
name="content"
placeholder="리뷰 내용"
/>
<div className={style.submit_container}>
<input
disabled={isPending}
required
name="author"
placeholder="작성자"
/>
<button disabled={isPending} type="submit">
{isPending ? "..." : "작성하기"}
</button>
</div>
</form>
</section>
);
}
기존 ReviewEditor 컴포넌트를 서버 컴포넌트에서 클라이언트 컴포넌트로 전환한다.
React 19버전에서 추가된 useActionState Hook. form 태그의 핸들링을 쉽게 도와주는 유용한 기능들을 제공한다.
const [state, formAction, isPending] = useActionState(
createReviewAction,
null
);
useActionState에는 핸들링 하려는 form의 action 함수, form State의 초기값을 인자로 받는다. 그리고 이를 받아 form State, Action 함수, 현재 로딩 상태를 반환하여 준다.
form에서 직접 받아 처리하던 createReviewAction는 useActionState의 인자로 이동하고, form에서는 useActionState에서 반환하는 Action 함수을 대신 action 옵션으로 전달해준다.
createReviewAction이 동작하고, 작업이 모두 종료되기 전까지 isPending에 의해 리뷰 입력 UI들이 동작하지 않도록 disabled 옵션을 적용해준다.
<button disabled={isPending} type="submit">
{isPending ? "..." : "작성하기"}
</button>
if (!bookId || !content || !author) {
return {
status: false,
error: "리뷰 내용과 작성자를 입력해주세요",
};
}
catch (err) {
return {
status: false,
error: `리뷰 저장에 실패했습니다 : ${err}`,
};
}
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/review/1`,
{
method: "POST",
body: JSON.stringify({ bookId, content, author }),
}
);
if (!response.ok) {
throw new Error(response.statusText);
}
useEffect(() => {
if (state && !state.status) {
alert(state.error);
}
}, [state]);
useEffect을 이용해서 서버 액션 자체가 실패했을 경우의 예외 처리를 해줄 수 있다.
기존 코드에서 에러가 발생하였을 때, 에러 메시지를 반환해주고 있는 점을 이용해서 alert 메소드로 사용자에게 에러 메시지를 출력하게 해줄 수 있다.
ReviewEditor 컴포넌트에서 반환되는 state는 에러 관련 데이터 밖에 없으니 조건문은 상황에 알맞게 작성하면 된다.
const [state, formAction, isPending] = useActionState(
createReviewAction,
null
);
export async function createReviewAction(_: any, formData: FormData) {
(...)
}
따라서 createReviewAction 함수에서는 첫 번째 매개변수를 state로 받아주어야 한다. 기존 함수에서 다른 값을 받아오고 있었다면 필히 변경해주어야 한다.
만약 state를 사용하지 않는다면 '_' 등의 이름으로 state 이름을 수정해서 사용하지 않는다는 점을 명확하게 해주어도 좋다.
