C.W.K.
Stream
Lesson 02 of 06 · published

useActionState — pending/error/result 공짜

~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 가 Action 을 state 관리로 감쌈. 모든 제출 결과가 새 state 됨. Pending 자동 추적. 더는 setIsLoading 춤 없음.

시그니처

const [state, formAction, isPending] = useActionState(actionFn, initialState)actionFn(prevState, formData) => Promise<newState>. Hook 이 반환:

  • state — 최신 state (첫 render 에 initial, 그 후 마지막 action 이 반환한 것).
  • formAction<form action={...}> 에 바인딩할 wrapped 함수.
  • isPending — Action 이 in-flight 동안 true.

Reducer-모양 action

본인 actionFn 이 FormData 도 받는 useReducer reducer 처럼 보임. 첫 인자가 이전 state; 두 번째가 제출된 FormData. 반환 값 (또는 resolve 된 promise 값) 이 새 state.

왜 이 모양? 에러와 success 가 같은 반환 경로 사용. 실패 시 { error: '...', input: ... } 반환 (재표시 위해 사용자 입력 보존), 성공 시 { data: ... }. 컴포넌트가 state 안 내용 기반 렌더 결정.

'이전 state 가 form 값 가짐' 패턴

서버 사이드 유효성 fail 시 사용자 제출 값과 함께 form 재표시 원함. Error state 의 일부로 반환. Form 의 uncontrolled input 이 React 가 재채움 (state 의 input 필드를 defaultValue 값으로 사용).

useActionState vs useState

useState 가 임의 setter 가진 임의 state 줌. useActionState 는 단일 action 의 success/failure 사이클에 묶인 state 줌. 합성: 클라이언트 전용 UI state (draft, 토글) 엔 useState, 제출 구동 state 엔 useActionState.

Action 이 곧 state machine. 각 제출이 state 를 '전' 에서 '후' 로 전환. Action 이 풀 next state (성공 또는 에러) 반환하게 만들면 제출 라이프사이클 전체를 함수 호출 하나로 collapse. Hook 이 나머지 처리.

Code

useActionState 로 title 저장·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 이 이메일 유효성 (regex 체크), 가짜 endpoint 에 POST, 실패 또는 성공 state 에 { email, error: 'invalid' | 'conflict' | null } 반환. Form 이 defaultValue={state.email} 로 input 재렌더해서 제출 간에 값 persist. Pending 중 input disable. Success 시 'Subscribed!' 메시지 토글.
Hint
Regex: /^[^@\s]+@[^@\s]+\.[^@\s]+$/. 충돌은 'taken' 포함된 이메일이 충돌 생산하게 hard-code 해서 경로 테스트.

Progress

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

댓글 0

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

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