fix: CRITICAL - Memoize all context values to stop infinite loops
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!
This commit is contained in:
@@ -265,7 +265,7 @@ function AddonRoute({ config }: { config: any }) {
|
||||
function Header({ onFullscreen, fullscreen, showToggle = true, scrollContainerRef }: { onFullscreen: () => void; fullscreen: boolean; showToggle?: boolean; scrollContainerRef?: React.RefObject<HTMLDivElement> }) {
|
||||
const [siteTitle, setSiteTitle] = React.useState((window as any).wnw?.siteTitle || 'WooNooW');
|
||||
const [isVisible, setIsVisible] = React.useState(true);
|
||||
const [lastScrollY, setLastScrollY] = React.useState(0);
|
||||
const lastScrollYRef = React.useRef(0);
|
||||
const isStandalone = window.WNW_CONFIG?.standaloneMode ?? false;
|
||||
|
||||
// Listen for store settings updates
|
||||
@@ -294,15 +294,15 @@ function Header({ onFullscreen, fullscreen, showToggle = true, scrollContainerRe
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentScrollY > lastScrollY && currentScrollY > 50) {
|
||||
if (currentScrollY > lastScrollYRef.current && currentScrollY > 50) {
|
||||
// Scrolling down & past threshold
|
||||
setIsVisible(false);
|
||||
} else if (currentScrollY < lastScrollY) {
|
||||
} else if (currentScrollY < lastScrollYRef.current) {
|
||||
// Scrolling up
|
||||
setIsVisible(true);
|
||||
}
|
||||
|
||||
setLastScrollY(currentScrollY);
|
||||
lastScrollYRef.current = currentScrollY;
|
||||
};
|
||||
|
||||
scrollContainer.addEventListener('scroll', handleScroll, { passive: true });
|
||||
@@ -310,7 +310,7 @@ function Header({ onFullscreen, fullscreen, showToggle = true, scrollContainerRe
|
||||
return () => {
|
||||
scrollContainer.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, [lastScrollY, scrollContainerRef]);
|
||||
}, [scrollContainerRef]);
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user