THE BIGGER PICTURE - Root Cause Analysis:
Problem Chain:
1. FABContext value recreated every render
2. All FAB consumers re-render
3. Dashboard re-renders
4. useFABConfig runs
5. Creates new icon/callbacks
6. Triggers FABContext update
7. INFINITE LOOP!
The Bug (in BOTH contexts):
<Context.Provider value={{ config, setFAB, clearFAB }}>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
NEW object every render!
Every time Provider re-renders:
- Creates NEW value object
- All consumers see "new" value
- All consumers re-render
- Causes more Provider re-renders
- INFINITE LOOP!
The Fix:
const setFAB = useCallback(..., []); // Stable function
const clearFAB = useCallback(..., []); // Stable function
const value = useMemo(() => ({ config, setFAB, clearFAB }), [config, setFAB, clearFAB]);
^^^^^^^
Only creates new object when dependencies actually change!
<Context.Provider value={value}>
^^^^^^^
Stable reference!
Why This is Critical:
Context is at the TOP of the component tree:
App
└─ FABProvider ← Bug here affects EVERYTHING below
└─ PageHeaderProvider ← Bug here too
└─ DashboardProvider
└─ Shell
└─ Dashboard ← Infinite re-renders
└─ Charts ← Break from constant re-renders
React Context Performance Rules:
1. ALWAYS memoize context value object
2. ALWAYS use useCallback for context functions
3. NEVER create inline objects in Provider value
4. Context updates trigger ALL consumers
Fixed Contexts:
1. FABContext - Memoized value, callbacks
2. PageHeaderContext - Memoized value, callbacks
Before:
Every render → new value object → all consumers re-render → LOOP
After:
Only config changes → new value object → consumers re-render once → done
Result:
✅ No infinite loops
✅ No unnecessary re-renders
✅ Clean console
✅ Smooth performance
✅ All features working
Files Modified:
- FABContext.tsx: Added useMemo and useCallback
- PageHeaderContext.tsx: Added useMemo and useCallback
- useFABConfig.tsx: Memoized icon and callbacks (previous fix)
- App.tsx: Fixed scroll detection with useRef (previous fix)
All infinite loop sources now eliminated!
42 lines
1.3 KiB
TypeScript
42 lines
1.3 KiB
TypeScript
import React, { createContext, useContext, useState, ReactNode, useMemo, useCallback } from 'react';
|
|
|
|
interface PageHeaderContextType {
|
|
title: string | null;
|
|
action: ReactNode | null;
|
|
setPageHeader: (title: string | null, action?: ReactNode) => void;
|
|
clearPageHeader: () => void;
|
|
}
|
|
|
|
const PageHeaderContext = createContext<PageHeaderContextType | undefined>(undefined);
|
|
|
|
export function PageHeaderProvider({ children }: { children: ReactNode }) {
|
|
const [title, setTitle] = useState<string | null>(null);
|
|
const [action, setAction] = useState<ReactNode | null>(null);
|
|
|
|
const setPageHeader = useCallback((newTitle: string | null, newAction?: ReactNode) => {
|
|
setTitle(newTitle);
|
|
setAction(newAction || null);
|
|
}, []);
|
|
|
|
const clearPageHeader = useCallback(() => {
|
|
setTitle(null);
|
|
setAction(null);
|
|
}, []);
|
|
|
|
const value = useMemo(() => ({ title, action, setPageHeader, clearPageHeader }), [title, action, setPageHeader, clearPageHeader]);
|
|
|
|
return (
|
|
<PageHeaderContext.Provider value={value}>
|
|
{children}
|
|
</PageHeaderContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function usePageHeader() {
|
|
const context = useContext(PageHeaderContext);
|
|
if (!context) {
|
|
throw new Error('usePageHeader must be used within PageHeaderProvider');
|
|
}
|
|
return context;
|
|
}
|