Useformstate

useFormState

⚠️ CANARY!

  • 해당 기능은 아직 React의 Canary/Experimental 채널에서만 사용 가능합니다.
  • 또한 해당 기능의 이점을 완전히 사용하려면 React의 서버 컴포넌트를 지원하는 프레임워크의 사용이 필요합니다.

📑 Summary

useFormState란 form 액션의 결과에 기반하여 상태를 업데이트 할 수 있게 해주는 훅 함수입니다.

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

📢 사용 시 주의할 점

import를 제대로 하자!

  • useFormStatereact가 아닌 react-dom에서 import 되어야 합니다.

React 서버 컴포넌트를 지원하는 프레임워크와 함께 사용할 경우

  • useFormStateform의 제출에 따른 서버의 응답값을 hydration 작업이 완료되기도 전에 보여지도록 해줍니다.
  • 따라서 서버 컴포넌트와 함께 사용할 때 useFormState는 JS가 클라이언트 측에서 실행되기 전에 form을 상호작용 가능하도록 만들어줍니다.
  • 하지만 서버 컴포넌트와 함께 사용하지 않는다면 이 기능은 컴포넌트의 일반 상태와 동일합니다.
👩‍💻 여기서 잠깐! hydration이란 무엇일까요?
  • Hydration이란, Server-Side에서 렌더링 된 정적 페이지(HTML)와 번들링 된 JS 파일을 클라이언트에게 보내면, Client-Side에서 HTML 코드와 JS(React) 코드를 서로 매칭시키는 과정을 의미합니다.

  • JS 코드들이 DOM 요소 위에 물을 채우듯 필요로 하던 요소들을 채운다하여 붙여진 이름이라고 하는데, 수분기 없는 정적 웹 페이지에 물을 주는 느낌이라고 이해하면 좋습니다.

  • hydration에 대해 자세히 학습하고 싶다면, Hydration 관련 포스트 (opens in a new tab)를 참고하세요!

🤔 어떻게 사용할 수 있을까요?

  1. form 액션이 호출되었을 때 업데이트 될 상태 생성을 위해 컴포넌트의 상단에 useFormState를 호출해줍니다.
  • form이 제출된 마지막 순간에 액션에서 반환된 값에 접근할 수 있습니다.
import { useFormState } from "react-dom";
 
function StatefulForm({}) {
  const [state, formAction] = useFormState(action, null);
  return (
    <form>
      {state}
      <button formAction={formAction}>Increment</button>
    </form>
  );
}
  1. useFormState에 초기 상태로서 이미 존재하는 form 액션 함수를 넘겨줄 수 있습니다.
  • 이때 useFormState는 당신의 form에서 사용하는 새로운 액션을 가장 최신의 form 상태와 함께 반환합니다.
  • 가장 최신의 form 상태는 또한 당신이 제공한 함수에도 전달됩니다.
import { useFormState } from "react-dom";
 
// form 액션 함수
async function increment(previousState, formData) {
  return previousState + 1;
}
 
function StatefulForm({}) {
  const [state, formAction] = useFormState(increment, 0);
  return (
    <form>
      {state}
      <button formAction={formAction}>Increment</button>
    </form>
  );
}

form 상태란?

  • form이 마지막으로 제출되었을 때 액션에 의해 반환된 값을 의미합니다.
  • 아직 form이 제출되지 않았다면 form의 상태는 당신이 전달한 초기 상태가 form의 상태가 됩니다.

action 함수

  • form이 제출되면 제공된 액션 함수가 호출되고, 이 함수의 반환 값은 이후 form의 현재 상태가 됩니다.
function action(currentState, formData) {
  // ...
  return "next state";
}
  • currentState
    • 액션 함수의 첫 번째 인자
    • form이 처음 제출 되었을 때에는 제공된 초기값이고, 이후 제출 과정에서는 액션이 가장 마지막에 호출되었을 때의 반환 값이 현재 상태 값이 됩니다.
  • 다른 인자들의 경우 useFormState를 사용하지 않았을 때와 동일하게 전달됩니다.

Parameters

ParameterDetail
fn- form이 제출되거나 버튼이 클릭되었을 때 호출될 함수
- 함수가 호출되면, form의 이전 상태값을 초기 인자로 받고 이후에는 form 액션이 일반적으로 받게 되는 인수들을 받습니다.
- form의 이전 상태는 처음에는 당신이 전달한 initialState가 될 것이고, 이후에는 이전 반환값이 될 것입니다.
initialState- 상태의 초기값
- 직렬화(메모리를 디스트에 저장하거나 네트워크 통신에 사용하기 위한 형식으로 변환하는 것)가 가능한 모든 값이 올 수 있습니다.
- 이 인수는 액션의 첫 번째 호출 이후에는 무시됩니다.
permalink (optional)- 이 form이 수정하게 될 고유한 페이지 URL을 포함하는 문자열
- 피드와 같이 동적인 콘텐츠가 있는 페이지에서 점진적 변화와 함께 사용할 경우
: 만약 fn이 서버 액션이고 form이 JS 번들이 로드되기 전에 제출되었다면 브라우저는 현재 페이지 URL이 아닌 특정된 permalink URL로 이동합니다.
- React가 상태를 어떻게 전달할지 알 수 있도록 동일한 액션 fnpermalink를 포함하는 같은 form 컴포넌트가 목적지인 페이지에서도 렌더링되어야 합니다.
- form이 hydrate 되고 나면 이 파라미터는 효력을 잃습니다.

반환값

  • 두 개의 값을 갖는 배열을 반환합니다.
    [현재 상태, 새로운 액션]
  1. 현재 상태
  • 처음에는 전달한 initialState와 일치합니다.
  • 액션이 호출된 이후(form이 제출된 이후)에는 당신이 제공한 액션에 의해 반환되는 값과 일치하게 됩니다.
  1. 새로운 액션
  • form 컴포넌트의 action prop으로 전달할 수 있습니다.
  • form 내부의 버튼 컴포넌트에 formAction prop으로 전달할 수 있습니다.

🧐 언제 사용할 수 있을까요?

📍 form 제출 이후 form 에러 보여주기

  • 서버 액션에 의해 반환되는 에러 메세지를 보여주려면 호출되는 액션을 useFormState로 감싸야 합니다.

form 컴포넌트

import { useState } from "react";
import { useFormState } from "react-dom";
import { addToCart } from "./actions.js";
 
function AddToCartForm({ quantity, itemTitle }) {
  // 초기값은 null이며 form 제출 시 addToCart fn이 실행됩니다.
  const [message, formAction] = useFormState(addToCart, null);
  return (
    <form action={formAction}>
      <h2>{itemTitle}</h2>
      <input type='hidden' name='quantity' value={quantity} />
      <button type='submit'>장바구니 추가</button>
      {message}
    </form>
  );
}
 
export default function App() {
  return (
    <>
      <h1>useFormState</h1>
      <AddToCartForm quantity='0' itemTitle='자바스크립트 Deep Dive' />
      <AddToCartForm quantity='10' itemTitle='리액트 Deep Dive' />
    </>
  );
}

action 함수

"use server";
 
export async function addToCart(prevState, queryData) {
  // 수량 정보를 받아옵니다.
  const quantity = queryData.get("quantity");
  // 수량의 존재 여부에 따라 다른 message 값을 반환합니다.
  if (quantity > 0) {
    return "정상적으로 추가되었습니다.";
  } else {
    return "🚨 Error! 상품이 매진 되어 추가할 수 없습니다.";
  }
}

결과

  • 각 버튼 클릭 시 해당 제품 수량에 따른 메시지가 버튼 옆에 렌더링 됩니다.
  • 만약 수량이 없을 경우 의도한 대로 에러 메시지가 보여지게 됩니다.

스크린샷 2024-02-13 234511

📍 form 제출 이후 구조화된 정보 보여주기

  • 서버 액션의 반환 값은 직렬화 가능한 값이 될 수 있습니다.

⛓️ 직렬화(Serialization)

  • 직렬화란, 앞에서도 짧게 언급했지만 데이터를 일련의 바이트로 변환하는 과정을 의미합니다.
    • 데이터는 원래의 형식을 유지하면서 전송 가능한 형태로 변환되어 파일에 저장하거나 네트워크를 통해 전송할 수 있게 됩니다.
  • 예를 들어, 숫자, 문자열, 배열, 객체 등의 데이터는 대부분 직렬화 될 수 있는 값에 해당합니다.
  • 서버 액션에서 반환하는 값은 이러한 데이터 형식에 해당할 수 있으므로 전송/저장이 가능합니다.
👩‍💻 서버 액션의 반환 값이 직렬화 가능하다는 것은, 해당 값이 전송 및 저장될 수 있는 형태로 변환될 수 있음을 의미합니다.

  • 반환 값을 아래의 예시와 같이 액션의 성공 여부와 실패 시 보여줄 에러 메시지, 성공 시 보여줄 업데이트 된 정보를 담은 객체 형태로 구현할 수도 있습니다.

form 컴포넌트

function AddToCartForm({ quantity, itemTitle }) {
  const [formState, formAction] = useFormState(addToCart, {});
  // 성공 여부에 따라 다른 값이 렌더링 되도록 구현할 수 있습니다.
  return (
    <form action={formAction}>
      <h2>{itemTitle}</h2>
      <input type='hidden' name='quantity' value={quantity} />
      <button type='submit'>장바구니 추가</button>
      {formState?.isSuccess && (
        <div className='toast'>
          🌟 장바구니에 추가되었습니다! 현재 장바구니에는 총{" "}
          {formState.cartSize}개의 물품이 담겨 있습니다.
        </div>
      )}
      {formState?.isSuccess === false && (
        <div className='error'>🚨 Error! {formState.message}</div>
      )}
    </form>
  );
}
 
// 위의 예시와 동일한 컴포넌트

action 함수

"use server";
 
export async function addToCart(prevState, queryData) {
  const quantity = queryData.get("quantity");
  // 반환 값이 단순 메시지가 아닌 객체 형태가 될 수 있습니다.
  if (quantity > 0) {
    return {
      isSuccess: true,
      cartSize: 12,
    };
  } else {
    return {
      isSuccess: false,
      message: "상품이 매진 되어 추가할 수 없습니다.",
    };
  }
}

결과

  • 실패 여부에 따라 다른 값이 렌더링되도록 boolean 값을 전달할 수 있습니다.
  • 결과에 따라 성공 시 업데이트 된 장바구니 물품 개수가, 실패 시 에러 메시지가 보여집니다.

스크린샷 2024-02-14 000229

🔎 References