본문 바로가기
C.W.K.
Stream
Lesson 02 of 06 · published

useActionState — 대기·오류·결과를 한곳에서

~14 min · useactionstate, react-19, actions

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
useActionState는 액션의 결과와 대기 상태를 함께 관리해. 제출할 때마다 수동으로 로딩 flag를 켰다 끄는 코드를 줄여 보자.

시그니처

const [state, formAction, isPending] = useActionState(actionFn, initialState)에서 actionFn(prevState, formData) => Promise<newState> 모양이야. 훅은 다음 세 값을 반환해.

  • state: 첫 렌더링에는 initialState, 이후에는 마지막 액션이 반환한 최신 상태야.
  • formAction: <form action={...}>에 바로 연결할 수 있도록 감싼 함수야.
  • isPending: 액션이 진행 중일 때 true가 돼.

reducer와 닮은 액션

actionFn은 FormData까지 받는 reducer와 비슷해. 첫 번째 인자는 이전 상태고 두 번째 인자는 제출된 FormData야. 함수가 반환한 값이나 프로미스가 이행된 값이 다음 상태가 돼.

이 구조에서는 오류와 성공이 같은 반환 경로를 사용해. 유효성 검사에 실패하면 사용자가 입력한 값을 보존한 { error: '...', input: ... }을 반환하고, 성공하면 { data: ... }를 반환할 수 있어. 컴포넌트는 그 상태를 보고 다음 화면을 렌더링해.

이전 입력을 상태에 보존하는 패턴

서버 검증에 실패했을 때 form과 사용자의 입력을 다시 보여 주려면, 제출된 값을 오류 상태에 함께 담아 반환해. 그런 다음 uncontrolled input의 defaultValue에 상태의 입력 필드를 연결하면 React가 값을 다시 채울 수 있어.

useActionState와 useState의 경계

useState는 임의의 UI 상태와 setter를 제공해. useActionState는 하나의 액션이 거치는 대기, 성공, 실패 흐름에 상태를 묶어. draft나 토글 같은 클라이언트 UI에는 useState를 쓰고, 제출이 만드는 상태에는 useActionState를 쓰면 역할이 선명해져.

액션 자체를 상태 전이로 봐. 제출 한 번이 이전 상태를 성공 또는 오류 상태로 바꿔. 액션이 다음 화면에 필요한 상태 전체를 반환하게 만들면 제출 생명주기를 함수 하나로 표현할 수 있고, 훅이 나머지 연결을 맡아.

Code

useActionState로 제목 저장·tsx
import { useActionState } from "react";

type State = {
  title: string;
  error: string | null;
};

const initialState: State = { title: "", error: null };

async function saveTitle(_prev: State, formData: FormData): Promise<State> {
  const title = (formData.get("title") as string).trim();
  if (!title) return { title, error: "Title cannot be empty" };
  try {
    await fetch("/api/conversations/current/title", {
      method: "PATCH",
      body: JSON.stringify({ title }),
    });
    return { title, error: null };
  } catch (e) {
    return { title, error: (e as Error).message };
  }
}

export function TitleEditor() {
  const [state, formAction, isPending] = useActionState(saveTitle, initialState);
  return (
    <form action={formAction} className="space-y-2">
      <input
        name="title"
        defaultValue={state.title}
        disabled={isPending}
        className="w-full px-3 py-2 border"
      />
      {state.error && (
        <p className="text-danger text-sm">{state.error}</p>
      )}
      <button
        type="submit"
        disabled={isPending}
        className="px-4 py-2 bg-brand text-bg rounded disabled:opacity-50"
      >
        {isPending ? "Saving…" : "Save"}
      </button>
    </form>
  );
}

External links

Exercise

이메일 input이 있는 <SubscribeForm>을 만들고 useActionState에 연결해. Action은 정규식으로 이메일을 검사하고 가짜 endpoint에 POST한 뒤 { email, error: 'invalid' | 'conflict' | null } 상태를 반환하게 해. Form은 defaultValue={state.email}로 제출 사이의 값을 유지하고, 대기 중에는 input을 비활성화하며, 성공하면 'Subscribed!'를 보여 줘.
Hint
Regex: /^[^@\s]+@[^@\s]+\.[^@\s]+$/. 충돌은 'taken' 포함된 이메일이 충돌 생산하게 hard-code 해서 경로 테스트.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.