Files
WooNooW/admin-spa/src/contexts/PageHeaderContext.tsx
dwindown 824266044d 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!
2025-11-06 21:27:44 +07:00

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;
}