From 4dc446fe697d01a31784e6260b156de434cf5df3 Mon Sep 17 00:00:00 2001 From: "leonardo.santos" Date: Fri, 31 Jul 2026 15:41:39 -0300 Subject: [PATCH] fix(moderation): colisao de query key derrubava a pagina intermitentemente MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Causa raiz do "This page couldn't load" / React error #31 que so acontecia navegando entre abas (nunca num reload direto): a tela de moderacao usava a MESMA query key ["learning-track-overview", trackId] que 5 outras telas (trilhas, marcos, cursos, conquista, perfil publico) usam pra guardar o LearningTrackOverview cru — mas aqui a queryFn transformava o resultado pra so o titulo da secao (string). React Query cacheia por key, entao quem chegasse nessa chave depois de passar pela moderacao (ou vice-versa) podia receber o formato errado sem refetch (dado ainda fresco). Quando o objeto {track, sections} vazava pra onde se esperava uma string, {sectionTitle} tentava renderizar o objeto direto como filho do React -> crash. So acontecia as vezes (dependia da ordem/timing de navegacao) e sumia com reload porque isso limpa o cache em memoria. Fix: usa select do React Query em vez de transformar dentro da queryFn. Isso guarda o LearningTrackOverview cru no cache (compativel com todo mundo que usa a mesma key) e so extrai o titulo pra esse consumidor especifico, sem corromper o cache compartilhado. Co-Authored-By: Claude Sonnet 5 --- .../[language]/moderation/page-content.tsx | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/app/[language]/moderation/page-content.tsx b/src/app/[language]/moderation/page-content.tsx index c9ad371..6d167bc 100644 --- a/src/app/[language]/moderation/page-content.tsx +++ b/src/app/[language]/moderation/page-content.tsx @@ -33,7 +33,10 @@ import { Submission } from "@/services/api/types/submission"; import { Mission, MissionSubmission } from "@/services/api/types/mission"; import { Activity } from "@/services/api/types/activity"; import { GamificationProfile } from "@/services/api/types/gamification-profile"; -import { TrackItem } from "@/services/api/types/learning-track"; +import { + LearningTrackOverview, + TrackItem, +} from "@/services/api/types/learning-track"; import { Course } from "@/services/api/types/course"; import { Event, EventModality } from "@/services/api/types/event"; import { TRACK_ITEM_TYPE_BADGE } from "@/lib/track-colors"; @@ -108,18 +111,26 @@ function useTrackItem(trackItemId: string) { }); } +// Mesma chave ["learning-track-overview", trackId] usada em várias outras +// telas (trilhas, marcos, cursos, conquista, perfil público) — todas +// guardam o LearningTrackOverview cru nesse cache. Aqui usamos `select` +// pra extrair só o título da seção sem sobrescrever o cache compartilhado +// com um formato diferente (isso já causou um bug real: quem lesse essa +// chave depois de passar por aqui recebia uma string em vez do objeto +// completo, e tentar renderizar esse objeto direto quebrava a página com +// "Objects are not valid as a React child"). function useTrackSectionTitle(trackId: string, sectionId: string) { const fetch = useGetLearningTrackOverviewService(); return useQuery({ queryKey: ["learning-track-overview", trackId], queryFn: async () => { const { status, data } = await fetch({ id: trackId }); - if (status !== HTTP_CODES_ENUM.OK) return null; - return ( - data.sections.find((s) => s.section.id === sectionId)?.section.title ?? - null - ); + if (status === HTTP_CODES_ENUM.OK) return data as LearningTrackOverview; + return null; }, + select: (overview) => + overview?.sections.find((s) => s.section.id === sectionId)?.section + .title ?? null, staleTime: 10 * 60 * 1000, enabled: !!trackId && !!sectionId, });