- Memoize context value in SettingsSectionsProvider - Use ctxRef pattern in hook to avoid depending on ctx object in useLayoutEffect deps (ctx object changes on every render, causing effect → setState → re-render → effect loop)
56 lines
1.8 KiB
TypeScript
56 lines
1.8 KiB
TypeScript
import React, { createContext, useContext, useState, useCallback, useRef, useMemo } from 'react';
|
|
|
|
export interface SettingsSection {
|
|
id: string;
|
|
label: string;
|
|
}
|
|
|
|
interface SettingsSectionsContextValue {
|
|
sections: SettingsSection[];
|
|
activeSection: string;
|
|
scrollToSection: (id: string) => void;
|
|
scrollContainerRef: React.RefObject<HTMLDivElement>;
|
|
setSections: (sections: SettingsSection[]) => void;
|
|
setActiveSection: (id: string) => void;
|
|
setScrollToSection: (fn: (id: string) => void) => void;
|
|
}
|
|
|
|
const SettingsSectionsContext = createContext<SettingsSectionsContextValue | null>(null);
|
|
|
|
export function SettingsSectionsProvider({ children }: { children: React.ReactNode }) {
|
|
const [sections, setSections] = useState<SettingsSection[]>([]);
|
|
const [activeSection, setActiveSection] = useState('');
|
|
const [scrollFn, setScrollFn] = useState<((id: string) => void) | null>(null);
|
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
|
|
const scrollToSection = useCallback((id: string) => {
|
|
scrollFn?.(id);
|
|
}, [scrollFn]);
|
|
|
|
// setScrollToSection receives a function, so wrap in updater to avoid
|
|
// React interpreting it as a state updater function
|
|
const setScrollToSectionStable = useCallback((fn: (id: string) => void) => {
|
|
setScrollFn(() => fn);
|
|
}, []);
|
|
|
|
const value = useMemo(() => ({
|
|
sections,
|
|
activeSection,
|
|
scrollToSection,
|
|
scrollContainerRef,
|
|
setSections,
|
|
setActiveSection,
|
|
setScrollToSection: setScrollToSectionStable,
|
|
}), [sections, activeSection, scrollToSection, scrollContainerRef, setScrollToSectionStable]);
|
|
|
|
return (
|
|
<SettingsSectionsContext.Provider value={value}>
|
|
{children}
|
|
</SettingsSectionsContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useSettingsSectionsContext() {
|
|
return useContext(SettingsSectionsContext);
|
|
}
|