react-audit(família 4): C140 leftover — dispatch manual preserva texto digitado em erro de validação (ActivityUpdateForm.tsx:34, ActivityResultForm.tsx:60, DemandWorkflowCard.tsx:104, MunicipalityUpdateDeliberation.tsx:227)

This commit is contained in:
Francisco Solla 2026-08-25 12:08:03 -03:00
parent 101cd5d791
commit e273e9ecfa
4 changed files with 46 additions and 9 deletions

View file

@ -1,7 +1,7 @@
'use client'
import { useRouter } from 'next/navigation'
import { useActionState } from 'react'
import { startTransition, useActionState, type FormEvent } from 'react'
import { registerActivityResultFormAction } from '@/app/(campaign)/campanha/(app)/atividades/[slug]/resultFormActions'
import { CampaignFormActionMessage } from '@/components/campaign/shared/CampaignFormActionMessage'
@ -42,6 +42,14 @@ export const ActivityResultForm = ({
.join(' ')
: null
// C139 — manual dispatch: React 19 resets uncontrolled fields after any
// settled form action, reverting the typed result to `initialSummary` on
// a validation error. `startTransition` keeps `pending` correct.
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
startTransition(() => formAction(new FormData(event.currentTarget)))
}
return (
<Card>
<CardHeader>
@ -49,7 +57,7 @@ export const ActivityResultForm = ({
{recordedLabel ? <p className="text-sm text-muted-foreground">{recordedLabel}</p> : null}
</CardHeader>
<CardContent>
<form action={formAction} className="flex flex-col gap-4">
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<input type="hidden" name="activityId" value={activityId} />
{state.status !== 'success' ? (
<CampaignFormActionMessage state={state} errorTitle="Não foi possível registrar" />

View file

@ -1,7 +1,7 @@
'use client'
import { useRouter } from 'next/navigation'
import { useActionState, useRef } from 'react'
import { startTransition, useActionState, useRef, type FormEvent } from 'react'
import { createActivityUpdateFormAction } from '@/app/(campaign)/campanha/(app)/atividades/[slug]/updateFormActions'
import { CampaignFormActionMessage } from '@/components/campaign/shared/CampaignFormActionMessage'
@ -25,13 +25,21 @@ export const ActivityUpdateForm = ({ activityId }: { activityId: number }) => {
const bodyError = fieldError(state.fieldErrors, 'body')
// C139 — manual dispatch (no `action={formAction}`): React 19 resets
// uncontrolled fields after any settled form action, wiping the typed
// update on a validation error. `startTransition` keeps `pending` correct.
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
startTransition(() => formAction(new FormData(event.currentTarget)))
}
return (
<Card>
<CardHeader>
<CardTitle>Nova atualização</CardTitle>
</CardHeader>
<CardContent>
<form ref={formRef} action={formAction} className="flex flex-col gap-4">
<form ref={formRef} onSubmit={handleSubmit} className="flex flex-col gap-4">
<input type="hidden" name="activityId" value={activityId} />
{state.status !== 'success' ? (
<CampaignFormActionMessage state={state} errorTitle="Não foi possível enviar" />

View file

@ -1,6 +1,6 @@
'use client'
import { useActionState } from 'react'
import { startTransition, useActionState, type FormEvent } from 'react'
import { Alert, AlertDescription } from '@/components/ui/Alert'
import { Button } from '@/components/ui/button'
@ -63,6 +63,19 @@ export const DemandWorkflowCard = ({
(target !== 'aprovada' && target !== 'rejeitada'),
)
// C139 — manual dispatch (no `action={submitTransition}`/`action={submitCost}`):
// React 19 resets uncontrolled fields after any settled form action, wiping
// the typed decision note and cost on a validation error. `startTransition`
// keeps the pending flags correct for the imperative dispatch.
const handleTransitionSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
startTransition(() => submitTransition(new FormData(event.currentTarget)))
}
const handleCostSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
startTransition(() => submitCost(new FormData(event.currentTarget)))
}
if (readOnly) {
return (
<section
@ -101,7 +114,7 @@ export const DemandWorkflowCard = ({
</div>
{availableTransitions.length ? (
<form action={submitTransition} className="flex flex-col gap-3">
<form onSubmit={handleTransitionSubmit} className="flex flex-col gap-3">
<input type="hidden" name="demandId" value={demandID} />
<Field>
<FieldLabel htmlFor={`demand-note-${demandID}`}>Nota da decisão</FieldLabel>
@ -144,7 +157,7 @@ export const DemandWorkflowCard = ({
<p className="text-sm text-muted-foreground">Esta demanda já foi decidida.</p>
)}
<form action={submitCost} className="flex flex-col gap-2">
<form onSubmit={handleCostSubmit} className="flex flex-col gap-2">
<input type="hidden" name="demandId" value={demandID} />
<FieldLabel htmlFor={`demand-cost-${demandID}`}>
Custo estimado (R$) — controle interno

View file

@ -1,6 +1,6 @@
'use client'
import { useActionState, useEffect, useRef } from 'react'
import { startTransition, useActionState, useEffect, useRef, type FormEvent } from 'react'
import { CampaignFormActionMessage } from '@/components/campaign/shared/CampaignFormActionMessage'
import { Badge } from '@/components/ui/Badge'
@ -197,6 +197,14 @@ const CommentThread = ({
if (state.status === 'success' && formRef.current) formRef.current.reset()
}, [state])
// C139 — manual dispatch: React 19 resets uncontrolled fields after any
// settled form action, wiping the typed comment on a validation error.
// The success reset above stays explicit; `startTransition` keeps `pending`.
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
startTransition(() => submitAction(new FormData(event.currentTarget)))
}
return (
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">Discussão</p>
@ -224,7 +232,7 @@ const CommentThread = ({
</ul>
) : null}
{canComment ? (
<form ref={formRef} action={submitAction} className="flex flex-col gap-2">
<form ref={formRef} onSubmit={handleSubmit} className="flex flex-col gap-2">
<input type="hidden" name="updateId" value={updateId} />
<Textarea
name="body"