Give each row identity and a revision
Keep a stable row ID from the client or service, plus a revision that increases when the operator changes that row. When validation starts, capture the current revision and a new validation-attempt ID. When a response arrives, apply it only if its ID, revision, and attempt ID match the current row. Consume that attempt after completion so a late duplicate cannot overwrite a newer outcome.
React documents useReducer as
a way to put state update logic in one reducer. The important idea here is the
state contract, not the specific hook: a result is information about a
particular row revision.
Separate validation from import
Use states such as draft, pending, accepted, and rejected for a review
stage. A row becoming accepted means only that the known validation result
belongs to the visible revision. It does not mean a server imported the row.
Keep final import outcome, retry, and reconciliation under a separate operation
identity.
The included model exercises current acceptance, current rejection, a stale result after editing, a same-revision retry whose first response arrives late, independent rows, and the rule that every row must be accepted before submission:
npm test --prefix sites/reactjsx.com/evidence/P122
It is not browser-tested JSX and does not issue requests, parse CSV, or authorize an import.
Communicate the current status
Show status next to the row it describes, including a readable reason for a current rejection and a keyboard-reachable way to correct it. Here is illustrative JSX only; it has not been browser- or assistive-technology-tested:
<tr aria-describedby={`row-${row.id}-status`}>
<td><input aria-label={`Value for row ${row.id}`} value={row.value} onChange={onEdit} /></td>
<td id={`row-${row.id}-status`} role="status">
{row.status === "pending" ? "Checking this row" : row.message ?? row.status}
</td>
<td><button type="button" onClick={onEdit}>Correct row</button></td>
</tr>
W3C's status-message guidance explains that visible status messages which do not change context should be programmatically determinable for assistive technologies. Test the concrete markup in the target browsers and assistive technologies; this state model alone does not establish that result.
For the single-field form version of the stale-result problem, see Ignore Stale Async Validation Results in a React Form. For an operation whose final outcome is unknown, see Prevent a Double Submit While an Operation Outcome Is Unresolved.
Can the client decide that a row is safe to import?
No. The client can present a review state. The trusted service must validate the submitted data and enforce authorization again at the import boundary.