From 021e1c6f072bdc42a2f78c66f5b3508de1578380 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:35:30 +0000 Subject: [PATCH 1/2] fix: update CP immediately after predicting After submitting a prediction, fetch fresh post data (including updated CP) from the API, matching the reaffirm flow pattern. Previously, the predict flow relied solely on revalidatePath which didn't reliably update the client-side CP display. Closes #4437 Co-authored-by: Sylvain --- .../src/components/forecast_maker/index.tsx | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/front_end/src/components/forecast_maker/index.tsx b/front_end/src/components/forecast_maker/index.tsx index a10575e4cf..f7a6d88e62 100644 --- a/front_end/src/components/forecast_maker/index.tsx +++ b/front_end/src/components/forecast_maker/index.tsx @@ -1,9 +1,10 @@ "use client"; -import { FC } from "react"; +import { FC, useCallback, useEffect, useState } from "react"; import PredictionStatusMessage from "@/components/forecast_maker/prediction_status_message"; import { useAuth } from "@/contexts/auth_context"; +import ClientPostsApi from "@/services/api/posts/posts.client"; import { PostWithForecasts } from "@/types/post"; import { canPredictQuestion } from "@/utils/questions/predictions"; @@ -18,20 +19,43 @@ type Props = { const ForecastMaker: FC = ({ post, onPredictionSubmit }) => { const { user } = useAuth(); - const { group_of_questions: groupOfQuestions, conditional, question } = post; - const canPredict = canPredictQuestion(post, user); + const [currentPost, setCurrentPost] = useState(post); - const predictionMessage = ; + // Sync local state when the prop changes (e.g., from server re-renders) + useEffect(() => { + setCurrentPost(post); + }, [post]); + + // Wrap onPredictionSubmit to fetch fresh post data (with updated CP) + // after a prediction is submitted, matching the reaffirm flow pattern + const handlePredictionSubmit = useCallback(async () => { + try { + const freshPost = await ClientPostsApi.getPost(currentPost.id); + setCurrentPost(freshPost); + } catch { + // Silently fail - the CP will update on next page load + } + onPredictionSubmit?.(); + }, [currentPost.id, onPredictionSubmit]); + + const { + group_of_questions: groupOfQuestions, + conditional, + question, + } = currentPost; + const canPredict = canPredictQuestion(currentPost, user); + + const predictionMessage = ; if (groupOfQuestions) { return ( ); } @@ -39,11 +63,11 @@ const ForecastMaker: FC = ({ post, onPredictionSubmit }) => { if (conditional) { return ( ); } @@ -53,9 +77,9 @@ const ForecastMaker: FC = ({ post, onPredictionSubmit }) => { ); } From 4bb0bb71b30894522b06365163750f3231157a75 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:14:19 +0000 Subject: [PATCH 2/2] fix: lift post refetch to ForecasterQuestionView Move the post-prediction refetch out of ForecastMaker and into the parent ForecasterQuestionView so that siblings (DetailedQuestionCard / DetailedGroupCard) also render the fresh CP immediately, not just the forecast maker subtree. Also avoids double-fetching in the prediction flow, where prediction_flow_post already refetches on submit, and protects against stale in-flight responses via a request-id ref. Addresses review feedback from @ncarazon and @coderabbitai on #4504. Co-authored-by: Sylvain --- .../forecaster_question_view/index.tsx | 52 ++++++++++++++++--- .../src/components/forecast_maker/index.tsx | 39 ++++---------- 2 files changed, 55 insertions(+), 36 deletions(-) diff --git a/front_end/src/app/(main)/questions/[id]/components/question_view/forecaster_question_view/index.tsx b/front_end/src/app/(main)/questions/[id]/components/question_view/forecaster_question_view/index.tsx index 0e27865546..138f1e1831 100644 --- a/front_end/src/app/(main)/questions/[id]/components/question_view/forecaster_question_view/index.tsx +++ b/front_end/src/app/(main)/questions/[id]/components/question_view/forecaster_question_view/index.tsx @@ -1,8 +1,11 @@ -import { Fragment } from "react"; +"use client"; + +import { Fragment, useCallback, useEffect, useRef, useState } from "react"; import DetailedGroupCard from "@/components/detailed_question_card/detailed_group_card"; import DetailedQuestionCard from "@/components/detailed_question_card/detailed_question_card"; import ForecastMaker from "@/components/forecast_maker"; +import ClientPostsApi from "@/services/api/posts/posts.client"; import { PostStatus, PostWithForecasts } from "@/types/post"; import { isGroupOfQuestionsPost, @@ -20,20 +23,55 @@ const ForecasterQuestionView: React.FC = ({ postData, preselectedGroupQuestionId, }) => { - const isResolved = postData.status === PostStatus.RESOLVED; - const isGroup = isGroupOfQuestionsPost(postData); + const [currentPost, setCurrentPost] = useState(postData); + const latestRequestIdRef = useRef(0); + + // Sync local state when the server prop changes (e.g., after a + // revalidatePath-driven re-render). + useEffect(() => { + setCurrentPost(postData); + }, [postData]); + + // Refetch fresh post data (including updated CP) after a prediction is + // submitted, so sibling components (DetailedQuestionCard / + // DetailedGroupCard) render the updated CP immediately instead of waiting + // for revalidatePath to land. + const handlePredictionSubmit = useCallback(async () => { + const requestId = ++latestRequestIdRef.current; + try { + const freshPost = await ClientPostsApi.getPost(postData.id); + // Ignore stale responses if a newer request has been issued meanwhile. + if (requestId === latestRequestIdRef.current) { + setCurrentPost(freshPost); + } + } catch (err) { + // Surface the error for observability; the CP will still update on + // the next revalidation / page load. + console.error("Failed to refresh post after prediction submit:", err); + } + }, [postData.id]); + + const isResolved = currentPost.status === PostStatus.RESOLVED; + const isGroup = isGroupOfQuestionsPost(currentPost); return ( - - {isQuestionPost(postData) && } + + {isQuestionPost(currentPost) && ( + + )} {isGroup && ( )} - {(!isResolved || isGroup) && } + {(!isResolved || isGroup) && ( + + )} ); }; diff --git a/front_end/src/components/forecast_maker/index.tsx b/front_end/src/components/forecast_maker/index.tsx index f7a6d88e62..77b43526a2 100644 --- a/front_end/src/components/forecast_maker/index.tsx +++ b/front_end/src/components/forecast_maker/index.tsx @@ -1,10 +1,9 @@ "use client"; -import { FC, useCallback, useEffect, useState } from "react"; +import { FC } from "react"; import PredictionStatusMessage from "@/components/forecast_maker/prediction_status_message"; import { useAuth } from "@/contexts/auth_context"; -import ClientPostsApi from "@/services/api/posts/posts.client"; import { PostWithForecasts } from "@/types/post"; import { canPredictQuestion } from "@/utils/questions/predictions"; @@ -19,43 +18,25 @@ type Props = { const ForecastMaker: FC = ({ post, onPredictionSubmit }) => { const { user } = useAuth(); - const [currentPost, setCurrentPost] = useState(post); - - // Sync local state when the prop changes (e.g., from server re-renders) - useEffect(() => { - setCurrentPost(post); - }, [post]); - - // Wrap onPredictionSubmit to fetch fresh post data (with updated CP) - // after a prediction is submitted, matching the reaffirm flow pattern - const handlePredictionSubmit = useCallback(async () => { - try { - const freshPost = await ClientPostsApi.getPost(currentPost.id); - setCurrentPost(freshPost); - } catch { - // Silently fail - the CP will update on next page load - } - onPredictionSubmit?.(); - }, [currentPost.id, onPredictionSubmit]); const { group_of_questions: groupOfQuestions, conditional, question, - } = currentPost; - const canPredict = canPredictQuestion(currentPost, user); + } = post; + const canPredict = canPredictQuestion(post, user); - const predictionMessage = ; + const predictionMessage = ; if (groupOfQuestions) { return ( ); } @@ -63,11 +44,11 @@ const ForecastMaker: FC = ({ post, onPredictionSubmit }) => { if (conditional) { return ( ); } @@ -77,9 +58,9 @@ const ForecastMaker: FC = ({ post, onPredictionSubmit }) => { ); }