diff --git a/packages/docusaurus-theme-common/src/hooks/useMutationObserver.ts b/packages/docusaurus-theme-common/src/hooks/useMutationObserver.ts index ecb16d6195ca..c752f5bb64da 100644 --- a/packages/docusaurus-theme-common/src/hooks/useMutationObserver.ts +++ b/packages/docusaurus-theme-common/src/hooks/useMutationObserver.ts @@ -4,8 +4,12 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -import {useEffect} from 'react'; -import {useEvent, useShallowMemoObject} from '../utils/reactUtils'; +import {useEffect, useMemo} from 'react'; +import { + useEvent, + useShallowMemoObject, + useShallowMemoArray, +} from '../utils/reactUtils'; type Options = MutationObserverInit; @@ -23,10 +27,21 @@ export function useMutationObserver( ): void { const stableCallback = useEvent(callback); - // MutationObserver options are not nested much - // so this should be to memo options in 99% - // TODO handle options.attributeFilter array - const stableOptions: Options = useShallowMemoObject(options); + // Memoize attributeFilter array separately for proper deep comparison + const stableAttributeFilter = useShallowMemoArray(options.attributeFilter); + + // Memoize remaining options (shallow comparison is fine for booleans) + const {attributeFilter: _, ...restOptions} = options; + const stableRestOptions = useShallowMemoObject(restOptions); + + // Combine memoized parts + const stableOptions: Options = useMemo( + () => + stableAttributeFilter + ? {...stableRestOptions, attributeFilter: stableAttributeFilter} + : stableRestOptions, + [stableRestOptions, stableAttributeFilter], + ); useEffect(() => { const observer = new MutationObserver(stableCallback); diff --git a/packages/docusaurus-theme-common/src/utils/reactUtils.tsx b/packages/docusaurus-theme-common/src/utils/reactUtils.tsx index 80e349e3ba8f..ca30ac00b80e 100644 --- a/packages/docusaurus-theme-common/src/utils/reactUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/reactUtils.tsx @@ -88,6 +88,17 @@ export function useShallowMemoObject(obj: O): O { return useMemo(() => obj, deps.flat()); } +/** + * Shallow-memoize an array. Returns the same array reference if elements + * are shallowly equal to the previous render. + * + * @param arr + */ +export function useShallowMemoArray(arr: T[] | undefined): T[] | undefined { + // eslint-disable-next-line react-compiler/react-compiler,react-hooks/exhaustive-deps + return useMemo(() => arr, arr ?? []); +} + type SimpleProvider = ComponentType<{children: ReactNode}>; /**