컨ν
μ€νΈ λΈλ‘
| 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 μΆκ°/μ ν νΌ
μ₯μ μλ리μ€
- μ§μ μ μ₯μ λ μμ. λ¨, λ€μκ³Ό κ°μ μ½λ νμ§ μ΄μ:
- λμΌν
useState/useTransition 보μΌλ¬νλ μ΄νΈ 5+ κ³³μ λ°λ³΅
- νΌ μλ¬λ₯Ό κ° μ»΄ν¬λνΈκ° μ체 stateλ‘ κ΄λ¦¬ β
useActionStateμ state.errorλ‘ νμ€ν κ°λ₯
- 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 λμ
: λ€λ₯Έ μΆμν β νμ΅ λΉμ©, μ κ· μμ‘΄μ± μ¦κ°
μμ© κΈ°μ€
μ°Έμ‘°
μ¬ν λ°©λ²
μ¬μ 쑰건
λ¨κ³
grep -r \"useActionState\\|useFormStatus\\|useOptimistic\" frontend/src/
- κ²°κ³Ό μΉ΄μ΄νΈ νμΈ
κΈ°λ κ²°κ³Ό
μ£Όμ νΌ/μ‘μ
μμ μ κ· ν
μ¬μ© λ€μ
μ€μ κ²°κ³Ό
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`
컨ν μ€νΈ λΈλ‘
ISS-UI-12β React 19 features not used (useOptimistic/useFormStatus/useActionState for server actions)μμ½
useActionState,useFormStatus,useOptimisticκ°μ React 19 μ κ· ν μ΄ κ±°μ νμ©λμ§ μμ. λμ μλuseState+useTransitionμ‘°ν©μΌλ‘ pending state κ΄λ¦¬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.tsxEvidence
frontend/package.jsonfrontend/src/app/(auth)/login/EmailLoginForm.tsxdisabled={isLoading}κ°μ μλ pending state κ΄λ¦¬frontend/src/app/(auth)/register/page.tsxdisabled={isPending}μλ κ΄λ¦¬frontend/src/app/actions/**useActionStateλ―Έμ¬μ©frontend/src/features/admin/components/UserTable.tsxisLoading(user.id)μλ μΆμ βuseOptimisticλ‘ μ¦μ λ°μ κ°λ₯μν₯ λΆμ
μν₯ λ²μ
μ₯μ μλ리μ€
useState/useTransition보μΌλ¬νλ μ΄νΈ 5+ κ³³μ λ°λ³΅useActionStateμstate.errorλ‘ νμ€ν κ°λ₯useOptimisticλ‘ μ¦μ λ³κ²½ κ°λ₯κΈ΄κΈλ
μ μ ν΄κ²° λ°©μ
μ κ·Ό λ°©λ²
EmailLoginForm β useActionState λμ μμ:
UserTable β useOptimistic λμ :
SubmitButton β useFormStatus νμ€ν:
λμ
μμ© κΈ°μ€
useActionStateλ‘ λ§μ΄κ·Έλ μ΄μ (μλ²)useOptimisticλ‘ λ§μ΄κ·Έλ μ΄μ docs/μ ADR νμμΌλ‘ κΈ°λ‘ (λλ README 보κ°)cd frontend && pnpm testμ°Έμ‘°
μ¬ν λ°©λ²
μ¬μ 쑰건
λ¨κ³
grep -r \"useActionState\\|useFormStatus\\|useOptimistic\" frontend/src/κΈ°λ κ²°κ³Ό
μ£Όμ νΌ/μ‘μ μμ μ κ· ν μ¬μ© λ€μ
μ€μ κ²°κ³Ό
0건 λλ κ·Ήμμ β μλ useState/useTransition ν¨ν΄ μΌμ
κ΄λ ¨ μ½λ 컨ν μ€νΈ
frontend/src/app/(auth)/login/EmailLoginForm.tsxfrontend/src/app/(auth)/register/page.tsxfrontend/src/features/admin/components/UserTable.tsxfrontend/src/app/actions/auth.ts