From 2d53c15c142585a6c48d7a36ac7c95329f4a4010 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 10:12:14 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20React=20Contexts?= =?UTF-8?q?=20and=20State=20Management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Memoize all Context Provider values and functions to reduce unnecessary re-renders. - Replace redundant `useState`/`useEffect` with derived state in `Chat.tsx`. - Fix `MapDataProvider` shadowing bug by moving provider to page level and removing redundant instances. - Document performance patterns in `.jules/bolt.md`. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com> --- .jules/bolt.md | 4 ++++ components/calendar-toggle-context.tsx | 13 +++++++++---- components/chat.tsx | 16 ++++++---------- components/history-toggle-context.tsx | 14 ++++++++++---- components/map-loading-context.tsx | 14 ++++++++++++-- components/map-toggle-context.tsx | 13 +++++++++---- components/map/map-data-context.tsx | 9 +++++++-- components/profile-toggle-context.tsx | 18 ++++++++++++------ components/usage-toggle-context.tsx | 14 ++++++++++---- 9 files changed, 79 insertions(+), 36 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 00000000..13dd8715 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,4 @@ +## 2025-05-14 - [React Context & Provider Shadowing] +**Learning:** Redundant context providers can cause "shadowing" where children update an inner provider's state while the parent consumes an outer provider's stale state. This often leads to bugs and unnecessary re-renders. Additionally, unmemoized context values cause all consumers to re-render whenever the provider re-renders, even if the consumed state hasn't changed. + +**Action:** Always memoize context value objects with `useMemo` and functions with `useCallback`. Audit the component tree to remove redundant providers, ensuring a single source of truth for each context while maintaining proper state isolation (e.g., keeping chat-specific data at the page/chat level rather than the root layout if session isolation is required). diff --git a/components/calendar-toggle-context.tsx b/components/calendar-toggle-context.tsx index 7cd2d5e1..6763515d 100644 --- a/components/calendar-toggle-context.tsx +++ b/components/calendar-toggle-context.tsx @@ -1,6 +1,6 @@ 'use client' -import { createContext, useContext, useState, ReactNode, useTransition } from 'react' +import { createContext, useContext, useState, ReactNode, useTransition, useMemo, useCallback } from 'react' interface CalendarToggleContextType { isCalendarOpen: boolean @@ -21,14 +21,19 @@ export const CalendarToggleProvider = ({ children }: { children: ReactNode }) => const [isPending, startTransition] = useTransition() const [isCalendarOpen, setIsCalendarOpen] = useState(false) - const toggleCalendar = () => { + const toggleCalendar = useCallback(() => { startTransition(() => { setIsCalendarOpen(prevState => !prevState) }) - } + }, []) + + const value = useMemo(() => ({ + isCalendarOpen, + toggleCalendar + }), [isCalendarOpen, toggleCalendar]) return ( - + {children} ) diff --git a/components/chat.tsx b/components/chat.tsx index e675f124..2749c931 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -17,7 +17,7 @@ import { useProfileToggle, ProfileToggleEnum } from "@/components/profile-toggle import { useUsageToggle } from "@/components/usage-toggle-context"; import SettingsView from "@/components/settings/settings-view"; import { UsageView } from "@/components/usage-view"; -import { MapDataProvider, useMapData } from './map/map-data-context'; // Add this and useMapData +import { useMapData } from './map/map-data-context'; // Add this and useMapData import { updateDrawingContext } from '@/lib/actions/chat'; // Import the server action import dynamic from 'next/dynamic' import { HeaderSearchButton } from './header-search-button' @@ -36,7 +36,7 @@ export function Chat({ id }: ChatProps) { const { isUsageOpen } = useUsageToggle(); const { isCalendarOpen } = useCalendarToggle() const [input, setInput] = useState('') - const [showEmptyScreen, setShowEmptyScreen] = useState(false) + const showEmptyScreen = messages.length === 0 const [isSubmitting, setIsSubmitting] = useState(false) const [suggestions, setSuggestions] = useState(null) const chatPanelRef = useRef(null); @@ -49,10 +49,6 @@ export function Chat({ id }: ChatProps) { chatPanelRef.current?.submitForm(); }; - useEffect(() => { - setShowEmptyScreen(messages.length === 0) - }, [messages]) - useEffect(() => { // Check if device is mobile const checkMobile = () => { @@ -125,7 +121,7 @@ export function Chat({ id }: ChatProps) { // Mobile layout if (isMobile) { return ( - {/* Add Provider */} + <>
@@ -165,13 +161,13 @@ export function Chat({ id }: ChatProps) { )}
-
+ ); } // Desktop layout return ( - {/* Add Provider */} + <>
{/* This is the new div for scrolling */} @@ -211,6 +207,6 @@ export function Chat({ id }: ChatProps) { {activeView ? : isUsageOpen ? : }
-
+ ); } diff --git a/components/history-toggle-context.tsx b/components/history-toggle-context.tsx index 9e4d38e9..9c43795f 100644 --- a/components/history-toggle-context.tsx +++ b/components/history-toggle-context.tsx @@ -1,6 +1,6 @@ 'use client' -import { createContext, useContext, useState, ReactNode } from "react" +import { createContext, useContext, useState, ReactNode, useMemo, useCallback } from "react" interface HistoryToggleContextType { isHistoryOpen: boolean @@ -13,11 +13,17 @@ const HistoryToggleContext = createContext export const HistoryToggleProvider: React.FC<{ children: ReactNode }> = ({ children }) => { const [isHistoryOpen, setIsHistoryOpen] = useState(false) - const toggleHistory = () => setIsHistoryOpen(prev => !prev) - const setHistoryOpen = (open: boolean) => setIsHistoryOpen(open) + const toggleHistory = useCallback(() => setIsHistoryOpen(prev => !prev), []) + const setHistoryOpen = useCallback((open: boolean) => setIsHistoryOpen(open), []) + + const value = useMemo(() => ({ + isHistoryOpen, + toggleHistory, + setHistoryOpen + }), [isHistoryOpen, toggleHistory, setHistoryOpen]) return ( - + {children} ) diff --git a/components/map-loading-context.tsx b/components/map-loading-context.tsx index 872555ba..b962ac5d 100644 --- a/components/map-loading-context.tsx +++ b/components/map-loading-context.tsx @@ -1,5 +1,5 @@ 'use client'; -import { createContext, useContext, useState, ReactNode } from 'react'; +import { createContext, useContext, useState, ReactNode, useMemo, useCallback } from 'react'; interface MapLoadingContextType { isMapLoaded: boolean; @@ -10,8 +10,18 @@ const MapLoadingContext = createContext(undef export const MapLoadingProvider = ({ children }: { children: ReactNode }) => { const [isMapLoaded, setIsMapLoaded] = useState(false); + + const setIsMapLoadedCallback = useCallback((isLoaded: boolean) => { + setIsMapLoaded(isLoaded); + }, []); + + const value = useMemo(() => ({ + isMapLoaded, + setIsMapLoaded: setIsMapLoadedCallback + }), [isMapLoaded, setIsMapLoadedCallback]); + return ( - + {children} ); diff --git a/components/map-toggle-context.tsx b/components/map-toggle-context.tsx index e3d3b552..1e548466 100644 --- a/components/map-toggle-context.tsx +++ b/components/map-toggle-context.tsx @@ -1,6 +1,6 @@ 'use client' -import React, { createContext, useContext, useState, ReactNode } from 'react'; +import React, { createContext, useContext, useState, ReactNode, useMemo, useCallback } from 'react'; export enum MapToggleEnum { FreeMode, @@ -22,12 +22,17 @@ interface MapToggleProviderProps { export const MapToggleProvider: React.FC = ({ children }) => { const [mapToggleState, setMapToggle] = useState(MapToggleEnum.FreeMode); - const setMapType = (type: MapToggleEnum) => { + const setMapType = useCallback((type: MapToggleEnum) => { setMapToggle(type); - } + }, []) + + const value = useMemo(() => ({ + mapType: mapToggleState, + setMapType + }), [mapToggleState, setMapType]) return ( - + {children} ); diff --git a/components/map/map-data-context.tsx b/components/map/map-data-context.tsx index 9b102547..130b840e 100644 --- a/components/map/map-data-context.tsx +++ b/components/map/map-data-context.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { createContext, useContext, useState, ReactNode } from 'react'; +import React, { createContext, useContext, useState, ReactNode, useMemo } from 'react'; // Define the shape of the map data you want to share export interface CameraState { center: { lat: number; lng: number }; @@ -41,8 +41,13 @@ const MapDataContext = createContext(undefined); export const MapDataProvider: React.FC<{ children: ReactNode }> = ({ children }) => { const [mapData, setMapData] = useState({ drawnFeatures: [], markers: [] }); + const value = useMemo(() => ({ + mapData, + setMapData + }), [mapData]) + return ( - + {children} ); diff --git a/components/profile-toggle-context.tsx b/components/profile-toggle-context.tsx index 83685f15..45f110d6 100644 --- a/components/profile-toggle-context.tsx +++ b/components/profile-toggle-context.tsx @@ -1,6 +1,6 @@ // components/ 'use client' -import { createContext, useContext, useState, ReactNode } from "react" +import { createContext, useContext, useState, ReactNode, useMemo, useCallback } from "react" //import profile-toggle-context.tsx; export enum ProfileToggleEnum { @@ -25,16 +25,22 @@ interface ProfileToggleProviderProps { export const ProfileToggleProvider: React.FC = ({ children }) => { const [activeView, setActiveView] = useState(null) - const toggleProfileSection = (section: ProfileToggleEnum) => { + const toggleProfileSection = useCallback((section: ProfileToggleEnum) => { setActiveView(prevView => (prevView === section ? null : section)) - } + }, []) - const closeProfileView = () => { + const closeProfileView = useCallback(() => { setActiveView(null) - } + }, []) + + const value = useMemo(() => ({ + activeView, + toggleProfileSection, + closeProfileView + }), [activeView, toggleProfileSection, closeProfileView]) return ( - + {children} ) diff --git a/components/usage-toggle-context.tsx b/components/usage-toggle-context.tsx index febe2680..1b9b2b72 100644 --- a/components/usage-toggle-context.tsx +++ b/components/usage-toggle-context.tsx @@ -1,6 +1,6 @@ 'use client' -import { createContext, useContext, useState, ReactNode } from "react" +import { createContext, useContext, useState, ReactNode, useMemo, useCallback } from "react" interface UsageToggleContextType { isUsageOpen: boolean @@ -13,11 +13,17 @@ const UsageToggleContext = createContext(und export const UsageToggleProvider: React.FC<{ children: ReactNode }> = ({ children }) => { const [isUsageOpen, setIsUsageOpen] = useState(false) - const toggleUsage = () => setIsUsageOpen(prev => !prev) - const closeUsage = () => setIsUsageOpen(false) + const toggleUsage = useCallback(() => setIsUsageOpen(prev => !prev), []) + const closeUsage = useCallback(() => setIsUsageOpen(false), []) + + const value = useMemo(() => ({ + isUsageOpen, + toggleUsage, + closeUsage + }), [isUsageOpen, toggleUsage, closeUsage]) return ( - + {children} )