From 1b15a4e18c7147db36dd8e5e4b676470c07631ec Mon Sep 17 00:00:00 2001 From: Yunxiao Xu Date: Tue, 17 Feb 2026 02:43:19 -0800 Subject: [PATCH] fix: Resolve infinite render loop by memoizing context functions and values --- frontend/src/App.tsx | 43 +++++++++++++--------- frontend/src/components/theme-provider.tsx | 23 ++++++++---- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3a35911..0a4ecb3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, createContext, useContext } from "react" +import { useState, useEffect, createContext, useContext, useMemo, useCallback } from "react" import { Routes, Route } from "react-router-dom" import { MainLayout } from "./components/layout/MainLayout" import { LoginForm } from "./components/auth/LoginForm" @@ -25,8 +25,15 @@ function AuthProvider({ children }: { children: React.ReactNode }) { const [isAuthenticated, setIsAuthenticated] = useState(false) const [user, setUser] = useState(null) + const value = useMemo(() => ({ + isAuthenticated, + setIsAuthenticated, + user, + setUser + }), [isAuthenticated, user]) + return ( - + {children} ) @@ -81,16 +88,16 @@ function AppContent() { initAuth() }, [setIsAuthenticated, setThemeLocal, setUser]) - const loadHistory = async () => { + const loadHistory = useCallback(async () => { try { const history = await ChatService.listConversations() setConversations(history) } catch (err) { console.error("Failed to load conversation history:", err) } - } + }, []) - const handleAuthSuccess = async () => { + const handleAuthSuccess = useCallback(async () => { try { const userData = await AuthService.getMe() setUser(userData) @@ -102,9 +109,9 @@ function AppContent() { } catch (err: unknown) { console.error("Failed to fetch user profile after login:", err) } - } + }, [setUser, setIsAuthenticated, setThemeLocal, loadHistory]) - const handleLogout = async () => { + const handleLogout = useCallback(async () => { try { await AuthService.logout() } catch (err: unknown) { @@ -117,9 +124,9 @@ function AppContent() { setThreadMessages({}) setThemeLocal("light") } - } + }, [setIsAuthenticated, setUser, setThemeLocal]) - const handleSelectConversation = async (id: string) => { + const handleSelectConversation = useCallback(async (id: string) => { setSelectedThreadId(id) try { const msgs = await ChatService.getMessages(id) @@ -127,9 +134,9 @@ function AppContent() { } catch (err) { console.error("Failed to fetch messages:", err) } - } + }, []) - const handleCreateConversation = async () => { + const handleCreateConversation = useCallback(async () => { try { const newConv = await ChatService.createConversation() setConversations(prev => [newConv, ...prev]) @@ -138,18 +145,18 @@ function AppContent() { } catch (err) { console.error("Failed to create conversation:", err) } - } + }, []) - const handleRenameConversation = async (id: string, name: string) => { + const handleRenameConversation = useCallback(async (id: string, name: string) => { try { const updated = await ChatService.renameConversation(id, name) setConversations(prev => prev.map(c => c.id === id ? updated : c)) } catch (err) { console.error("Failed to rename conversation:", err) } - } + }, []) - const handleDeleteConversation = async (id: string) => { + const handleDeleteConversation = useCallback(async (id: string) => { try { await ChatService.deleteConversation(id) setConversations(prev => prev.filter(c => c.id !== id)) @@ -164,11 +171,11 @@ function AppContent() { } catch (err) { console.error("Failed to delete conversation:", err) } - } + }, [selectedThreadId]) - const handleMessagesFinal = (id: string, messages: MessageResponse[]) => { + const handleMessagesFinal = useCallback((id: string, messages: MessageResponse[]) => { setThreadMessages(prev => ({ ...prev, [id]: messages })) - } + }, []) const queryParams = new URLSearchParams(window.location.search) const externalError = queryParams.get("error") diff --git a/frontend/src/components/theme-provider.tsx b/frontend/src/components/theme-provider.tsx index 4bf0316..e928b93 100644 --- a/frontend/src/components/theme-provider.tsx +++ b/frontend/src/components/theme-provider.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, useState } from "react" +import { createContext, useContext, useEffect, useState, useCallback, useMemo } from "react" import { AuthService } from "@/services/auth" export type Theme = "light" | "dark" @@ -29,7 +29,7 @@ export function ThemeProvider({ root.classList.add(theme) }, [theme]) - const setTheme = async (newTheme: Theme) => { + const setTheme = useCallback(async (newTheme: Theme) => { setThemeState(newTheme) if (isAuthenticated) { try { @@ -38,18 +38,25 @@ export function ThemeProvider({ console.error("Failed to sync theme to backend:", error) } } - } + }, [isAuthenticated]) - const setThemeLocal = (newTheme: Theme) => { + const setThemeLocal = useCallback((newTheme: Theme) => { setThemeState(newTheme) - } + }, []) - const toggleTheme = () => { + const toggleTheme = useCallback(() => { setTheme(theme === "light" ? "dark" : "light") - } + }, [theme, setTheme]) + + const value = useMemo(() => ({ + theme, + setTheme, + setThemeLocal, + toggleTheme + }), [theme, setTheme, setThemeLocal, toggleTheme]) return ( - + {children} )