학습 Next.js - Day 22 / 클라이언트 컴포넌트에서의 서버 액션

이유승·2024년 10월 13일

Next.js 학습

목록 보기
23/27



1. 클라이언트 컴포넌트에서의 서버 액션

  • 클라이언트 컴포넌트에서 서버 액션을 호출하여 로딩 상태 설정, 에러 핸들링 등의 기능을 구현할 수 있다.

  • 서버에서 데이터가 응답되어 화면에 렌더링되기까지, 시간적 지연 동안 사용자에게 '무언가'를 보여주지 않으면 답답한 느낌을 받을 수 밖에 없다.

  • 특히나 로딩 중인 상태에서는 추가적인 액션을 막아두지 않으면, 작업 하나가 미처 종료되기 전에 중복된 요청이 발생할 수도 있다.



2. 실습

"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>
  • button의 경우 isPending 값을 이용해서 버튼의 텍스트를 더 명확하게 보여주는 방법도 좋다.



useActionState과 에러 핸들링

  • useActionState을 사용한다면 에러 핸들링도 조금 더 세밀하게 구현할 수 있다.
  if (!bookId || !content || !author) {
    return {
      status: false,
      error: "리뷰 내용과 작성자를 입력해주세요",
    };
  }

catch (err) {
    return {
      status: false,
      error: `리뷰 저장에 실패했습니다 : ${err}`,
    };
  }
  • 예외 처리에서 에러가 발생했다면, status 값과 error 값을 반환하도록 해준다.
    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);
    }
  • 데이터 페칭 함수에서는 Error 객체를 던져주는 방식을 사용한다.
  useEffect(() => {
    if (state && !state.status) {
      alert(state.error);
    }
  }, [state]);
  • useEffect을 이용해서 서버 액션 자체가 실패했을 경우의 예외 처리를 해줄 수 있다.

  • 기존 코드에서 에러가 발생하였을 때, 에러 메시지를 반환해주고 있는 점을 이용해서 alert 메소드로 사용자에게 에러 메시지를 출력하게 해줄 수 있다.

  • ReviewEditor 컴포넌트에서 반환되는 state는 에러 관련 데이터 밖에 없으니 조건문은 상황에 알맞게 작성하면 된다.



useActionState를 사용할 때 데이터 페칭 함수에서의 주의점.

const [state, formAction, isPending] = useActionState(
    createReviewAction,
    null
);
  • createReviewAction은 첫번째 인자로 state를 받아간다.
export async function createReviewAction(_: any, formData: FormData) {
	(...)
}
  • 따라서 createReviewAction 함수에서는 첫 번째 매개변수를 state로 받아주어야 한다. 기존 함수에서 다른 값을 받아오고 있었다면 필히 변경해주어야 한다.

  • 만약 state를 사용하지 않는다면 '_' 등의 이름으로 state 이름을 수정해서 사용하지 않는다는 점을 명확하게 해주어도 좋다.









00. 강의 소개.

profile
프론트엔드 개발자를 준비하고 있습니다.

0개의 댓글