mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
Resolve the settle verdict in render, not in an effect
Same guarded transition, expressed as the render-phase state adjustment the hooks lint rule asks for: a resolved settle clears the state in the same committed render that carries the outcome, so the falling-edge toast still sees both flips at once and the block runs at most once per settle.
This commit is contained in:
1 parent
b2908343c4
commit
408c1f599a
1 file changed
+11
-12
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { InsightResult } from "@/lib/ai/types";
|
||||
// Type-only — the runtime schemas load lazily inside `fetchAdvisor` so
|
||||
@@ -476,25 +476,24 @@ export function useInsightsAdvisorQuery(
|
||||
|
||||
// Apply the settle resolution the pure seam proves. Each poll result lands
|
||||
// in `query.data` (the poll's own cache write IS the fresh payload — no
|
||||
// second setQueryData needed); the seam decides fresh / settle-failed /
|
||||
// keep-waiting, and the strip toasts on the falling edge of the combined
|
||||
// busy state. The effect keys on `dataUpdatedAt`, not the payload object:
|
||||
// structural sharing keeps an unchanged payload reference-identical across
|
||||
// polls, and the attempt cap must still advance on every poll.
|
||||
const settlePayload = query.data?.payload;
|
||||
const settleDataUpdatedAt = query.dataUpdatedAt;
|
||||
useEffect(() => {
|
||||
if (!settling) return;
|
||||
// second setQueryData needed) and re-renders this hook; the seam decides
|
||||
// fresh / settle-failed / keep-waiting. Render-phase state adjustment (the
|
||||
// React-documented alternative to a setState-in-effect): guarded so it
|
||||
// fires at most once per settle — resolution non-null clears `settling`,
|
||||
// and the next render skips the whole block. Both flips land in the SAME
|
||||
// committed render, so the strip's falling-edge toast sees the proven
|
||||
// outcome exactly when the busy state drops.
|
||||
if (settling) {
|
||||
const resolution = resolveAdvisorSettle(
|
||||
settling,
|
||||
settlePayload,
|
||||
query.data?.payload,
|
||||
readDataUpdateCount(),
|
||||
);
|
||||
if (resolution) {
|
||||
setSettling(null);
|
||||
setSettleOutcome(resolution);
|
||||
}
|
||||
}, [settling, settlePayload, settleDataUpdatedAt, readDataUpdateCount]);
|
||||
}
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: () => fetchAdvisor({ force: true }),
|
||||
|
||||
Reference in new issue
Block a user