Skip to content

🟑[P2] feat(frontend): adopt React 19 useActionState / useFormStatus / useOptimistic for server actions #86

Description

@teddylee777

μ»¨ν…μŠ€νŠΈ 블둝

Key Value
Category frontend
Checklist ISS-UI-12 β€” React 19 features not used (useOptimistic/useFormStatus/useActionState for server actions)
Priority P2 🟑
Scan Date 2026-04-16
Flagged By main-session verification

μš”μ•½

  • WHAT: React 19.1을 μ‚¬μš© μ€‘μ΄μ§€λ§Œ νΌΒ·μ„œλ²„ μ•‘μ…˜μ— useActionState, useFormStatus, useOptimistic 같은 React 19 μ‹ κ·œ 훅이 거의 ν™œμš©λ˜μ§€ μ•ŠμŒ. λŒ€μ‹  μˆ˜λ™ useState + useTransition μ‘°ν•©μœΌλ‘œ pending state 관리
  • WHY: μ‹ κ·œ 훅듀은 (1) λ³΄μΌλŸ¬ν”Œλ ˆμ΄νŠΈ κ°μ†Œ, (2) μžλ™ pending μƒνƒœ, (3) form-level μ—λŸ¬ 처리 ν‘œμ€€ν™”, (4) 낙관적 μ—…λ°μ΄νŠΈλ‘œ UX ν–₯상을 제곡. 미적용 μ‹œ νšŒμ›κ°€μž…/둜그인/admin 폼 λ“± 곳곳에 반볡 μ½”λ“œ 쑴재
  • WHERE: frontend/src/app/(auth)/login/EmailLoginForm.tsx, (auth)/register/page.tsx, (main)/admin/components/SettingsForm.tsx, shared/components/settings/ConnectionList.tsx, features/admin/components/UserTable.tsx
  • SEVERITY: MEDIUM (κ°œμ„ ) β€” κΈ°λŠ₯ 결함은 μ•„λ‹ˆλ‚˜ React 19 best practice λ―Έμ€€μˆ˜, μ½”λ“œ ν’ˆμ§ˆ 영ν–₯

Evidence

# File Line Finding Flagged By Confidence
1 frontend/package.json β€” React 19.1 μ‚¬μš© 쀑 β€” μ‹ κ·œ ν›… μ‚¬μš© κ°€λŠ₯ main-session High
2 frontend/src/app/(auth)/login/EmailLoginForm.tsx β€” disabled={isLoading} 같은 μˆ˜λ™ pending state 관리 @code-review High
3 frontend/src/app/(auth)/register/page.tsx β€” disabled={isPending} μˆ˜λ™ 관리 @code-review High
4 frontend/src/app/actions/** β€” server actions μ •μ˜, ν΄λΌμ΄μ–ΈνŠΈ 호좜 μ‹œ useActionState λ―Έμ‚¬μš© main-session Medium
5 frontend/src/features/admin/components/UserTable.tsx β€” isLoading(user.id) μˆ˜λ™ 좔적 β€” useOptimistic둜 μ¦‰μ‹œ 반영 κ°€λŠ₯ @code-review Medium

영ν–₯ 뢄석

영ν–₯ λ²”μœ„

  • λͺ¨λ“  인증 폼 (둜그인/νšŒμ›κ°€μž…/이메일/API ν‚€)
  • admin μ‚¬μš©μž μ•‘μ…˜ (suspend/approve/reactivate) β€” 낙관적 μ—…λ°μ΄νŠΈ 적용 μ‹œ 즉각적 UX
  • ConnectionList μΆ”κ°€/μ „ν™˜ 폼

μž₯μ•  μ‹œλ‚˜λ¦¬μ˜€

  • 직접적 μž₯μ• λŠ” μ—†μŒ. 단, λ‹€μŒκ³Ό 같은 μ½”λ“œ ν’ˆμ§ˆ 이슈:
    1. λ™μΌν•œ useState/useTransition λ³΄μΌλŸ¬ν”Œλ ˆμ΄νŠΈ 5+ 곳에 반볡
    2. 폼 μ—λŸ¬λ₯Ό 각 μ»΄ν¬λ„ŒνŠΈκ°€ 자체 state둜 관리 β€” useActionState의 state.error둜 ν‘œμ€€ν™” κ°€λŠ₯
    3. UserTable의 "Suspend" 클릭 μ‹œ μ„œλ²„ μ‘λ‹΅κΉŒμ§€ row μƒνƒœ λ³€κ²½ μ•ˆλ¨ β€” useOptimistic둜 μ¦‰μ‹œ λ³€κ²½ κ°€λŠ₯

긴급도

  • μ‹ κ·œ κΈ°λŠ₯ μΆ”κ°€ μ‹œ μ μ§„μ μœΌλ‘œ λ„μž… (μƒˆ 폼뢀터)
  • κΈ°μ‘΄ μ½”λ“œ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜μ€ 별도 PR둜 뢄리 κ°€λŠ₯

μ œμ•ˆ ν•΄κ²° λ°©μ•ˆ

μ ‘κ·Ό 방법

EmailLoginForm β€” useActionState λ„μž… μ˜ˆμ‹œ:

import { useActionState } from \"react\";

export function EmailLoginForm() {
  const [state, formAction, isPending] = useActionState(loginAction, {
    error: null,
    success: false,
  });

  return (
    <form action={formAction}>
      <input name=\"email\" />
      {state.error && <div className=\"text-red-500\">{state.error}</div>}
      <button disabled={isPending}>
        {isPending ? \"Signing in...\" : \"Sign in\"}
      </button>
    </form>
  );
}

UserTable β€” useOptimistic λ„μž…:

const [optimisticUsers, applyOptimistic] = useOptimistic(
  users,
  (state, action: { id: string; status: UserStatus }) =>
    state.map(u => u.id === action.id ? { ...u, status: action.status } : u)
);

const handleSuspend = (userId: string) => {
  applyOptimistic({ id: userId, status: \"suspended\" });
  startTransition(() => suspendAction(userId));
};

SubmitButton β€” useFormStatus ν‘œμ€€ν™”:

function SubmitButton({ children }: { children: React.ReactNode }) {
  const { pending } = useFormStatus();
  return <button disabled={pending}>{pending ? \"…\" : children}</button>;
}

λŒ€μ•ˆ

  • ν˜„ μ½”λ“œ μœ μ§€: λ™μž‘μ€ ν•˜μ§€λ§Œ React 19 베슀트 ν”„λž™ν‹°μŠ€ λ―Έμ€€μˆ˜
  • react-hook-form λ„μž…: λ‹€λ₯Έ 좔상화 β€” ν•™μŠ΅ λΉ„μš©, μ‹ κ·œ μ˜μ‘΄μ„± 증가

수용 κΈ°μ€€

  • μ΅œμ†Œ 1개 폼(EmailLoginForm λ˜λŠ” RegisterPage)을 useActionState둜 λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ (μ‹œλ²”)
  • 1개 admin μ•‘μ…˜ (suspend/approve)을 useOptimistic둜 λ§ˆμ΄κ·Έλ ˆμ΄μ…˜
  • λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ νŒ¨ν„΄μ„ docs/에 ADR ν˜•μ‹μœΌλ‘œ 기둝 (λ˜λŠ” README 보강)
  • ν–₯ν›„ μ‹ κ·œ 폼은 React 19 νŒ¨ν„΄ μ‚¬μš© ꢌμž₯ λͺ…μ‹œ
  • ν…ŒμŠ€νŠΈ μ»€λ§¨λ“œ: cd frontend && pnpm test

μ°Έμ‘°

μž¬ν˜„ 방법

사전 쑰건

  • ν˜„ μ‹œμ  μ½”λ“œ μƒνƒœ

단계

  1. grep -r \"useActionState\\|useFormStatus\\|useOptimistic\" frontend/src/
  2. κ²°κ³Ό 카운트 확인

κΈ°λŒ€ κ²°κ³Ό

μ£Όμš” 폼/μ•‘μ…˜μ—μ„œ μ‹ κ·œ ν›… μ‚¬μš© λ‹€μˆ˜

μ‹€μ œ κ²°κ³Ό

0건 λ˜λŠ” κ·Ήμ†Œμˆ˜ β€” μˆ˜λ™ useState/useTransition νŒ¨ν„΄ 일색

κ΄€λ ¨ μ½”λ“œ μ»¨ν…μŠ€νŠΈ

File Role Relevance
frontend/src/app/(auth)/login/EmailLoginForm.tsx 이메일 둜그인 폼 μ‹œλ²” λ§ˆμ΄κ·Έλ ˆμ΄μ…˜
frontend/src/app/(auth)/register/page.tsx νšŒμ›κ°€μž… νŽ˜μ΄μ§€ μ‹œλ²” λ§ˆμ΄κ·Έλ ˆμ΄μ…˜
frontend/src/features/admin/components/UserTable.tsx admin μ‚¬μš©μž ν…Œμ΄λΈ” useOptimistic 적용
frontend/src/app/actions/auth.ts 인증 server actions useActionState와 κ²°ν•©

Detected by oh-my-braincrew `omb:issue` scan
Category: frontend | Scan date: 2026-04-16
`omb-issue-scan category=frontend checklist=ISS-UI-12`

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions