Files
backspace/packages/web/src/components/modals/SettingsSectionsContext.tsx
T
Jannis Braun fbf83cf8f4 fix: prevent infinite re-render loop in settings sections
- 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)
2026-03-22 03:32:43 +01:00

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