fix(web): stop rectification effects from setting state on mount
eslint-config-next failed staging lint because the case snapshot helper and choice-card reset called setState from useEffect. Load the snapshot in the fetch callback and remount the card by question id instead. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -306,6 +306,27 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
|
||||
// Candidate snapshot comes from the persisted Candidate Snapshot API, never
|
||||
// from parsing agent text or hidden sentinels.
|
||||
const applyCaseSnapshot = useCallback((payload: {
|
||||
latest_result?: unknown;
|
||||
choice_card?: unknown;
|
||||
case?: { accepted_time?: unknown; confirmed_time?: unknown };
|
||||
} | null) => {
|
||||
if (!payload) return;
|
||||
const nextCandidate = parseRectificationCandidateResult(payload.latest_result);
|
||||
const nextChoice = parseRectificationChoiceCard(payload.choice_card);
|
||||
const acceptedTime = typeof payload.case?.accepted_time === "string" ? payload.case.accepted_time : null;
|
||||
const confirmedTime = typeof payload.case?.confirmed_time === "string" ? payload.case.confirmed_time : null;
|
||||
setCandidateResult(nextCandidate);
|
||||
setChoiceCard(nextChoice);
|
||||
if (confirmedTime) {
|
||||
setSavedTime(confirmedTime);
|
||||
setSavedStatus("confirmed");
|
||||
} else if (acceptedTime) {
|
||||
setSavedTime(acceptedTime);
|
||||
setSavedStatus("accepted");
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadCaseSnapshot = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
@@ -313,32 +334,27 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
{ cache: "no-store" },
|
||||
);
|
||||
if (!response.ok) return;
|
||||
const payload = await response.json().catch(() => null);
|
||||
const nextCandidate = parseRectificationCandidateResult(payload?.latest_result);
|
||||
const nextChoice = parseRectificationChoiceCard(payload?.choice_card);
|
||||
const acceptedTime = typeof payload?.case?.accepted_time === "string" ? payload.case.accepted_time : null;
|
||||
const confirmedTime = typeof payload?.case?.confirmed_time === "string" ? payload.case.confirmed_time : null;
|
||||
setCandidateResult(nextCandidate);
|
||||
setChoiceCard(nextChoice);
|
||||
if (confirmedTime) {
|
||||
setSavedTime(confirmedTime);
|
||||
setSavedStatus("confirmed");
|
||||
} else if (acceptedTime) {
|
||||
setSavedTime(acceptedTime);
|
||||
setSavedStatus("accepted");
|
||||
}
|
||||
applyCaseSnapshot(await response.json().catch(() => null));
|
||||
} catch {
|
||||
// Snapshot refresh is best-effort; the durable Case remains on the server.
|
||||
}
|
||||
}, [caseId, sessionId]);
|
||||
}, [applyCaseSnapshot, caseId, sessionId]);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
void loadCaseSnapshot().then(() => {
|
||||
if (!active) return;
|
||||
});
|
||||
return () => { active = false; };
|
||||
}, [loadCaseSnapshot]);
|
||||
const controller = new AbortController();
|
||||
void fetch(
|
||||
`/api/rectification/cases/${encodeURIComponent(caseId)}?sessionId=${encodeURIComponent(sessionId)}`,
|
||||
{ cache: "no-store", signal: controller.signal },
|
||||
)
|
||||
.then((response) => (response.ok ? response.json() : null))
|
||||
.then((payload) => {
|
||||
if (!controller.signal.aborted) applyCaseSnapshot(payload);
|
||||
})
|
||||
.catch(() => {
|
||||
// Snapshot refresh is best-effort; the durable Case remains on the server.
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [applyCaseSnapshot, caseId, sessionId]);
|
||||
|
||||
const send = useCallback(async (action: "opening" | "message", messageText: string) => {
|
||||
const trimmed = action === "message" ? messageText.trim() : "";
|
||||
@@ -839,6 +855,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
)}
|
||||
{showChoiceCards && message.renderKey === choiceCardMessageKey && choiceCard && (
|
||||
<RectificationChoiceCard
|
||||
key={choiceCard.question_id}
|
||||
card={choiceCard}
|
||||
pending={busy}
|
||||
disabled={readonly}
|
||||
|
||||
@@ -20,10 +20,6 @@ export function RectificationChoiceCard(props: RectificationChoiceCardProps) {
|
||||
const primary = props.card.options.filter((option) => option.role === "primary");
|
||||
const secondary = props.card.options.filter((option) => option.role === "secondary");
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedKey("");
|
||||
}, [props.card.question_id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.pending) firstChoiceRef.current?.focus();
|
||||
}, [props.card.question_id, props.pending]);
|
||||
|
||||
Reference in New Issue
Block a user