본문 바로가기

기술스택/React.js

[React 19] useActionState

Form Action의 결과에 따라 상태를 업데이트(to update state based on the result of a form action)할 때 사용한다.

 

기본적인 형태는 form이 제출되거나 버튼을 누를 때 호출하는 함수, 초기값, form이 수정되는 unique한 페이지의 URL을 전달해준다.

 

리턴되는 값은 배열 형태이며, 초기상태 또는 action이 호출된 후에는 action의 반환된 값과 form 컴포넌트에 action props를 전달하거나 form 컴포넌트 내에 있는 버튼 컴포넌트에 formAction props를 전달할 수 있는 새로운 action의 형태이다.

isPending은 action을 처리하는동안 대기상태에 있는지를 나타낸다.

 

const [state, formAction, isPending] = useActionState(fn, initialState, permalink?);

 

form을 컨트롤하기 위한 커스텀훅을 아래와 같이 작성한다.

 

export async function useLogin(
  _: unknown, 
  formData: FormData
): Promise<{
  userId: string;
  password: string;
}> {
  const userId = formData.get("user-id")?.toString();
  const password = formData.get("password")?.toString();

  await new Promise((resolve) => {
    setTimeout(resolve, 2000);
  });

  return {
    userId,
    password
  }
}

 

로그인 처리를 위한 컴포넌트에서 useActionState 훅을 호출하고, 훅에 커스텀훅을 전달하여 로그인 폼을 컨트롤 할 수 있게 전달해준다.

이 때 "use client" 디렉티브를 붙여서 클라이언트 컴포넌트로 처리한다.

 

"use client";

import { useActionState } from "react"
import { useLogin } from "~/hooks/use-login"

export default function LoginForm() {

  const [userState, formAction, isPending] = useActionState(useLogin, null)

  return (
    <form action={formAction}>
      <label htmlFor="user-id"></label>
      <input type="text" id="user-id" name="user-id"  />
      <label htmlFor="password"></label>
      <input type="password" id="password" name="password"  />
      <button type="submit">로그인</button>
      {
        isPending 
          ? <p>loading...</p> 
          : <p>{userState?.userId}</p>
      }
    </form>
  )
}

 

사용상 참고할 점으로는

1. RSC(React Server Component)를 지원하는 프레임워크에서 useActionState를 사용하게 되면 JavaScript가 클라이언트에서 실행되기 전에 form을 interactive하게 만들 수 있다.

 

2. 다만 RSC를 지원하지 않는 프레임워크에서는 일반적인 상태관리를 위한 훅과 동일하게 작동한다.

 

3. useActionState에 전달되는 함수는 이전 상태 또는 초기 상태를 첫번째 argument로 받으며, useActionState를 사용하지 않고 직접 form action을 사용했을 때와는 다른 signature를 가진다. 다시 말해서 useActionState를 사용하면 이전 상태에 접근할 수 있지만, 직접 form action을 컨트롤하는 경우에는 그럴 수 없다.

 

// useActionState를 사용하는 경우
const [state, formAction] = useActionState((previousState, formData) => {
  // previousState를 사용할 수 있음
  return newState;
}, initialState);

// 직접 form action을 사용하는 경우
const handleSubmit = (formData) => {
  // previousState에 직접 접근할 수 없음
  return result;
};