From d29b19bc277078e3009e0196e05195017988c7ce Mon Sep 17 00:00:00 2001 From: OKWN Date: Sat, 23 May 2026 05:24:35 +0300 Subject: [PATCH] fix(eslint): resolve setState-in-effect errors in dashboard components (#1362) - LanguageSwitcher: remove mounted state + useLayoutEffect pattern Portal renders directly based on open state (SSR-safe without client check) - UsageStats: replace stats-null check with isInitialLoad ref to avoid setState in effect body (cascading render issue) --- gitbook/components/LanguageSwitcher.js | 7 +++---- src/shared/components/UsageStats.js | 11 ++++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/gitbook/components/LanguageSwitcher.js b/gitbook/components/LanguageSwitcher.js index d212af77..9dea2793 100644 --- a/gitbook/components/LanguageSwitcher.js +++ b/gitbook/components/LanguageSwitcher.js @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useLayoutEffect } from "react"; import { createPortal } from "react-dom"; import { useRouter, usePathname } from "next/navigation"; import { Globe, X } from "lucide-react"; @@ -15,12 +15,11 @@ function extractLangFromPath(pathname) { export default function LanguageSwitcher({ currentLang }) { const [open, setOpen] = useState(false); - const [mounted, setMounted] = useState(false); const router = useRouter(); const pathname = usePathname(); const current = getLanguage(currentLang); - useEffect(() => { + useLayoutEffect(() => { setMounted(true); }, []); @@ -91,7 +90,7 @@ export default function LanguageSwitcher({ currentLang }) { {current.flag} - {mounted && open && createPortal(modal, document.body)} + {open && createPortal(modal, document.body)} ); } diff --git a/src/shared/components/UsageStats.js b/src/shared/components/UsageStats.js index 67121929..69a55592 100644 --- a/src/shared/components/UsageStats.js +++ b/src/shared/components/UsageStats.js @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useMemo, useCallback } from "react"; +import { useState, useEffect, useMemo, useCallback, useRef } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import { FREE_PROVIDERS, AI_PROVIDERS } from "@/shared/constants/providers"; @@ -203,6 +203,7 @@ export default function UsageStats({ period: periodProp, setPeriod: setPeriodPro const [viewMode, setViewMode] = useState("costs"); const [providers, setProviders] = useState([]); const [periodLocal, setPeriodLocal] = useState("today"); + const isInitialLoad = useRef(true); const period = periodProp ?? periodLocal; const setPeriod = setPeriodProp ?? setPeriodLocal; @@ -231,8 +232,12 @@ export default function UsageStats({ period: periodProp, setPeriod: setPeriodPro // Fetch filtered stats via REST when period changes useEffect(() => { // First load: show full spinner; subsequent: show subtle fetching indicator - if (!stats) setLoading(true); - else setFetching(true); + if (isInitialLoad.current) { + isInitialLoad.current = false; + setLoading(true); + } else { + setFetching(true); + } fetch(`/api/usage/stats?period=${period}`) .then((r) => r.ok ? r.json() : null)