서버로부터의 편지, useFormState

762025년 10월 30일3

useFormStatus는 폼이 제출되는 ‘동안’의 문제를 해결했다. 하지만 여전히 마지막 퍼즐 조각이 남아 있었다. 서버 액션이 실행된 ‘후’의 결과, 즉 성공 메시지나 유효성 검사 오류 같은 피드백을 어떻게 다시 클라이언트로 가져와 보여줄 것인가 하는 문제였다.

이 비동기적인 피드백 루프를 구현하는 것은 결코 간단하지 않았다. 개발자들은 서버 액션의 결과를 어딘가에 저장하고, 그 결과를 클라이언트 컴포넌트가 다시 읽어와 상태를 업데이트하는 복잡한 과정을 거쳐야 했다.

“우리는 지금 서버와 클라이언트 사이에 끊어진 다리를 놓고 있습니다.”

로렌 탄이 회의에서 말했다. 그녀는 화이트보드에 클라이언트에서 서버로 향하는 action 화살표를 그렸다. 그리고 그 반대 방향, 서버에서 클라이언트로 돌아오는 길은 물음표로 남겨두었다.

“클라이언트가 서버에게 말을 거는 방법(Action)은 찾았지만, 서버가 클라이언트에게 답장하는 표준화된 방법이 없습니다. 이 답장을 전달할 우편배달부가 필요해요.”

그 ‘우편배달부’의 역할을 할 훅이 바로 useFormState였다.

useFormState의 설계는 서버 액션의 개념을 한 단계 확장시키는 것이었다. 서버 액션이 단순히 작업을 수행하고 끝나는 것이 아니라, ‘새로운 상태’를 반환할 수 있도록 만드는 것이다.

팀이 설계한 useFormState의 동작 흐름은 정교했다.

  1. 개발자는 useFormState(action, initialState)를 호출한다. action은 상태를 반환하도록 수정된 서버 액션이고, initialState는 폼의 초기 상태다.
  2. 이 훅은 두 가지를 반환한다: [state, formAction]. state는 현재 폼의 상태이고, formAction<form> 태그에 넘겨줄, React에 의해 특별하게 래핑된 새로운 액션 함수다.
  3. 사용자가 폼을 제출하면, 이 formAction이 실행된다.
  4. formAction은 내부적으로 원래의 action 함수를 호출하는데, 이때 현재 stateformData를 인자로 넘겨준다. action(currentState, formData)
  5. 서버에서 action이 실행된다. 로직에 따라 성공 또는 실패 메시지를 담은 ‘새로운 상태’ 객체를 반환한다.
  6. React는 서버로부터 반환된 이 새로운 상태를 받아, 클라이언트의 state 변수를 그 값으로 업데이트하고 리렌더링을 촉발시킨다.

이 흐름은 끊어졌던 다리를 완벽하게 연결했다.

클라이언트가 formAction을 통해 질문을 던지면, 서버는 그 질문에 대한 답을 ‘상태’라는 이름의 편지에 담아 돌려보낸다. 그리고 useFormState라는 우편배달부가 그 편지를 클라이언트의 state라는 우편함에 정확하게 배달해주는 것이다.

이로써, 개발자는 서버의 응답을 처리하기 위해 별도의 fetch나 상태 관리 로직을 짤 필요가 완전히 사라졌다.

// 서버의 응답을 받아 에러 메시지를 보여주는 코드
const [state, formAction] = useFormState(loginAction, { error: null });

// ...
<form action={formAction}>
  {/* ... */}
  {state.error && <p>{state.error}</p>}
</form>

이 몇 줄의 코드는 서버와 클라이언트가 상태를 통해 대화하는, 완전하고 아름다운 피드백 루프를 완성시켰다. useFormStatus가 과정의 UI를 책임졌다면, useFormState는 결과의 상태를 책임졌다. 이 두 훅이 마침내 한 쌍을 이루면서, React의 폼 처리는 시작부터 끝까지, 그 어떤 순간도 놓치지 않는 완전한 솔루션으로 거듭났다.