From 0fca7a746be2783228399e774bed051423197999 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Mon, 20 Apr 2026 11:56:39 +0200 Subject: [PATCH 1/3] refactor: memoize context values in use*ContextValues hooks Wraps context value objects in `React.useMemo` across react-context-selector consumer packages so Provider value identity stays stable across renders when dependencies are unchanged, preventing unnecessary consumer re-renders. Also extracts `use*ContextValues` hooks that were co-located with context creation into dedicated files under their component folders (color-picker, swatch-picker, carousel-slider, carousel-nav), and introduces a new `usePopoverContextValues_unstable` hook to replace the inline Provider object that was built directly in `renderPopover_unstable`. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Accordion/useAccordionContextValues.ts | 21 +++--- .../useAvatarGroupContextValues.ts | 14 ++-- .../useAvatarGroupPopoverContextValues.ts | 14 ++-- .../components/CarouselNav/CarouselNav.tsx | 2 +- .../CarouselNav/CarouselNavContext.ts | 9 +-- .../useCarouselNavContextValues.ts | 12 ++++ .../CarouselSlider/CarouselSlider.tsx | 2 +- .../CarouselSlider/CarouselSliderContext.ts | 14 +--- .../useCarouselSliderContextValues.ts | 12 ++++ .../CarouselViewport/CarouselViewport.tsx | 2 +- .../components/ColorPicker/ColorPicker.tsx | 2 +- .../useColorPickerContextValues.ts | 20 ++++++ .../library/src/contexts/colorPicker.ts | 15 +--- .../Dialog/useDialogContextValues.ts | 45 +++++++----- .../components/List/useListContextValues.ts | 13 ++-- .../components/Menu/useMenuContextValues.ts | 60 ++++++++++------ .../MenuList/useMenuListContextValues.ts | 23 ++++--- .../library/etc/react-popover.api.md | 10 ++- .../react-popover/library/src/Popover.ts | 2 + .../src/components/Popover/Popover.tsx | 4 +- .../library/src/components/Popover/index.ts | 2 + .../src/components/Popover/renderPopover.tsx | 43 ++---------- .../Popover/usePopoverContextValues.ts | 68 +++++++++++++++++++ .../react-popover/library/src/index.ts | 11 ++- .../library/src/popoverContext.ts | 2 +- .../components/SwatchPicker/SwatchPicker.tsx | 2 +- .../useSwatchPickerContextValues.ts | 23 +++++++ .../library/src/contexts/index.ts | 2 +- .../library/src/contexts/swatchPicker.ts | 18 +---- .../DataGrid/useDataGridContextValues.ts | 23 +++++-- .../TabList/useTabListContextValues.tsx | 44 ++++++++---- .../library/etc/react-teaching-popover.api.md | 3 +- ...useTeachingPopoverCarouselContextValues.ts | 18 +++-- .../Toolbar/useToolbarContextValues.tsx | 22 +++--- .../FlatTree/useFlatTreeContextValues.ts | 49 ++++++++----- .../components/Tree/useTreeContextValues.ts | 49 +++++++------ .../TreeItem/useTreeItemContextValues.ts | 51 +++++++++----- 37 files changed, 473 insertions(+), 253 deletions(-) create mode 100644 packages/react-components/react-carousel/library/src/components/CarouselNav/useCarouselNavContextValues.ts create mode 100644 packages/react-components/react-carousel/library/src/components/CarouselSlider/useCarouselSliderContextValues.ts create mode 100644 packages/react-components/react-color-picker/library/src/components/ColorPicker/useColorPickerContextValues.ts create mode 100644 packages/react-components/react-popover/library/src/components/Popover/usePopoverContextValues.ts create mode 100644 packages/react-components/react-swatch-picker/library/src/components/SwatchPicker/useSwatchPickerContextValues.ts diff --git a/packages/react-components/react-accordion/library/src/components/Accordion/useAccordionContextValues.ts b/packages/react-components/react-accordion/library/src/components/Accordion/useAccordionContextValues.ts index 29feb91003d0c..f11f2609bbf7b 100644 --- a/packages/react-components/react-accordion/library/src/components/Accordion/useAccordionContextValues.ts +++ b/packages/react-components/react-accordion/library/src/components/Accordion/useAccordionContextValues.ts @@ -1,17 +1,22 @@ +'use client'; + +import * as React from 'react'; import type { AccordionContextValue } from '../../contexts/accordion'; import type { AccordionContextValues, AccordionState } from './Accordion.types'; export function useAccordionContextValues_unstable(state: AccordionState): AccordionContextValues { const { navigation, openItems, requestToggle, multiple, collapsible } = state; - // This context is created with "@fluentui/react-context-selector", these is no sense to memoize it - const accordion: AccordionContextValue = { - navigation, - openItems, - requestToggle, - collapsible, - multiple, - }; + const accordion = React.useMemo( + () => ({ + navigation, + openItems, + requestToggle, + collapsible, + multiple, + }), + [navigation, openItems, requestToggle, collapsible, multiple], + ); return { accordion }; } diff --git a/packages/react-components/react-avatar/library/src/components/AvatarGroup/useAvatarGroupContextValues.ts b/packages/react-components/react-avatar/library/src/components/AvatarGroup/useAvatarGroupContextValues.ts index 62f19043ed27f..1994e457a8d0f 100644 --- a/packages/react-components/react-avatar/library/src/components/AvatarGroup/useAvatarGroupContextValues.ts +++ b/packages/react-components/react-avatar/library/src/components/AvatarGroup/useAvatarGroupContextValues.ts @@ -1,12 +1,18 @@ +'use client'; + +import * as React from 'react'; import type { AvatarGroupContextValue, AvatarGroupContextValues, AvatarGroupState } from '../AvatarGroup'; export const useAvatarGroupContextValues = (state: AvatarGroupState): AvatarGroupContextValues => { const { layout, size } = state; - const avatarGroup: AvatarGroupContextValue = { - layout, - size, - }; + const avatarGroup = React.useMemo( + () => ({ + layout, + size, + }), + [layout, size], + ); return { avatarGroup }; }; diff --git a/packages/react-components/react-avatar/library/src/components/AvatarGroupPopover/useAvatarGroupPopoverContextValues.ts b/packages/react-components/react-avatar/library/src/components/AvatarGroupPopover/useAvatarGroupPopoverContextValues.ts index b7003638ed2c3..eaf1ea5940af4 100644 --- a/packages/react-components/react-avatar/library/src/components/AvatarGroupPopover/useAvatarGroupPopoverContextValues.ts +++ b/packages/react-components/react-avatar/library/src/components/AvatarGroupPopover/useAvatarGroupPopoverContextValues.ts @@ -1,13 +1,19 @@ +'use client'; + +import * as React from 'react'; import type { AvatarGroupContextValue, AvatarGroupContextValues } from '../AvatarGroup/AvatarGroup.types'; import type { AvatarGroupPopoverState } from './AvatarGroupPopover.types'; export const useAvatarGroupPopoverContextValues_unstable = ( state: AvatarGroupPopoverState, ): AvatarGroupContextValues => { - const avatarGroup: AvatarGroupContextValue = { - isOverflow: true, - size: 24, - }; + const avatarGroup = React.useMemo( + () => ({ + isOverflow: true, + size: 24, + }), + [], + ); return { avatarGroup }; }; diff --git a/packages/react-components/react-carousel/library/src/components/CarouselNav/CarouselNav.tsx b/packages/react-components/react-carousel/library/src/components/CarouselNav/CarouselNav.tsx index 7ba8f1a7b2b26..fb1513f7a0392 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselNav/CarouselNav.tsx +++ b/packages/react-components/react-carousel/library/src/components/CarouselNav/CarouselNav.tsx @@ -4,7 +4,7 @@ import type { ForwardRefComponent } from '@fluentui/react-utilities'; import * as React from 'react'; import type { CarouselNavProps } from './CarouselNav.types'; -import { useCarouselNavContextValues_unstable } from './CarouselNavContext'; +import { useCarouselNavContextValues_unstable } from './useCarouselNavContextValues'; import { renderCarouselNav_unstable } from './renderCarouselNav'; import { useCarouselNav_unstable } from './useCarouselNav'; import { useCarouselNavStyles_unstable } from './useCarouselNavStyles.styles'; diff --git a/packages/react-components/react-carousel/library/src/components/CarouselNav/CarouselNavContext.ts b/packages/react-components/react-carousel/library/src/components/CarouselNav/CarouselNavContext.ts index 4f5a15c04d49a..0533a78014bfb 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselNav/CarouselNavContext.ts +++ b/packages/react-components/react-carousel/library/src/components/CarouselNav/CarouselNavContext.ts @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; -import type { CarouselNavContextValue, CarouselNavState } from './CarouselNav.types'; +import type { CarouselNavContextValue } from './CarouselNav.types'; const carouselNavContext = React.createContext(undefined); @@ -20,10 +20,3 @@ export const CarouselNavContextProvider = carouselNavContext.Provider; export type CarouselNavContextValues = { carouselNav: CarouselNavContextValue; }; - -export function useCarouselNavContextValues_unstable(state: CarouselNavState): CarouselNavContextValues { - const { appearance } = state; - const carouselNav = React.useMemo(() => ({ appearance }), [appearance]); - - return { carouselNav }; -} diff --git a/packages/react-components/react-carousel/library/src/components/CarouselNav/useCarouselNavContextValues.ts b/packages/react-components/react-carousel/library/src/components/CarouselNav/useCarouselNavContextValues.ts new file mode 100644 index 0000000000000..36966cdedc49a --- /dev/null +++ b/packages/react-components/react-carousel/library/src/components/CarouselNav/useCarouselNavContextValues.ts @@ -0,0 +1,12 @@ +'use client'; + +import * as React from 'react'; +import type { CarouselNavContextValues } from './CarouselNavContext'; +import type { CarouselNavState } from './CarouselNav.types'; + +export function useCarouselNavContextValues_unstable(state: CarouselNavState): CarouselNavContextValues { + const { appearance } = state; + const carouselNav = React.useMemo(() => ({ appearance }), [appearance]); + + return { carouselNav }; +} diff --git a/packages/react-components/react-carousel/library/src/components/CarouselSlider/CarouselSlider.tsx b/packages/react-components/react-carousel/library/src/components/CarouselSlider/CarouselSlider.tsx index 4e54d370257c6..ec132ae38f235 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselSlider/CarouselSlider.tsx +++ b/packages/react-components/react-carousel/library/src/components/CarouselSlider/CarouselSlider.tsx @@ -7,7 +7,7 @@ import { useCarouselSlider_unstable } from './useCarouselSlider'; import { renderCarouselSlider_unstable } from './renderCarouselSlider'; import { useCarouselSliderStyles_unstable } from './useCarouselSliderStyles.styles'; import type { CarouselSliderProps } from './CarouselSlider.types'; -import { useCarouselSliderContextValues_unstable } from './CarouselSliderContext'; +import { useCarouselSliderContextValues_unstable } from './useCarouselSliderContextValues'; import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts'; /** diff --git a/packages/react-components/react-carousel/library/src/components/CarouselSlider/CarouselSliderContext.ts b/packages/react-components/react-carousel/library/src/components/CarouselSlider/CarouselSliderContext.ts index b920876e8121a..b468aa28d5bbd 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselSlider/CarouselSliderContext.ts +++ b/packages/react-components/react-carousel/library/src/components/CarouselSlider/CarouselSliderContext.ts @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; -import type { CarouselSliderContextValue, CarouselSliderState } from './CarouselSlider.types'; +import type { CarouselSliderContextValue } from './CarouselSlider.types'; const carouselSliderContext = React.createContext(undefined); @@ -20,15 +20,3 @@ export const CarouselSliderContextProvider = carouselSliderContext.Provider; export type CarouselSliderContextValues = { carouselSlider: CarouselSliderContextValue; }; - -export function useCarouselSliderContextValues_unstable(state: CarouselSliderState): CarouselSliderContextValues { - const { cardFocus } = state; - const carouselSlider = React.useMemo( - () => ({ - cardFocus, - }), - [cardFocus], - ); - - return { carouselSlider }; -} diff --git a/packages/react-components/react-carousel/library/src/components/CarouselSlider/useCarouselSliderContextValues.ts b/packages/react-components/react-carousel/library/src/components/CarouselSlider/useCarouselSliderContextValues.ts new file mode 100644 index 0000000000000..d99aa3ed7c7f0 --- /dev/null +++ b/packages/react-components/react-carousel/library/src/components/CarouselSlider/useCarouselSliderContextValues.ts @@ -0,0 +1,12 @@ +'use client'; + +import * as React from 'react'; +import type { CarouselSliderContextValues } from './CarouselSliderContext'; +import type { CarouselSliderState } from './CarouselSlider.types'; + +export function useCarouselSliderContextValues_unstable(state: CarouselSliderState): CarouselSliderContextValues { + const { cardFocus } = state; + const carouselSlider = React.useMemo(() => ({ cardFocus }), [cardFocus]); + + return { carouselSlider }; +} diff --git a/packages/react-components/react-carousel/library/src/components/CarouselViewport/CarouselViewport.tsx b/packages/react-components/react-carousel/library/src/components/CarouselViewport/CarouselViewport.tsx index 7423c8a99e1c9..722b9aab7fb77 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselViewport/CarouselViewport.tsx +++ b/packages/react-components/react-carousel/library/src/components/CarouselViewport/CarouselViewport.tsx @@ -6,7 +6,7 @@ import { useCarouselViewport_unstable } from './useCarouselViewport'; import { renderCarouselViewport_unstable } from './renderCarouselViewport'; import { useCarouselViewportStyles_unstable } from './useCarouselViewportStyles.styles'; import type { CarouselViewportProps } from './CarouselViewport.types'; -import { useCarouselSliderContextValues_unstable } from '../CarouselSlider/CarouselSliderContext'; +import { useCarouselSliderContextValues_unstable } from '../CarouselSlider/useCarouselSliderContextValues'; import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts'; /** diff --git a/packages/react-components/react-color-picker/library/src/components/ColorPicker/ColorPicker.tsx b/packages/react-components/react-color-picker/library/src/components/ColorPicker/ColorPicker.tsx index 8f835d3b49440..f1509aec94d97 100644 --- a/packages/react-components/react-color-picker/library/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/react-components/react-color-picker/library/src/components/ColorPicker/ColorPicker.tsx @@ -6,7 +6,7 @@ import { useColorPicker_unstable } from './useColorPicker'; import { renderColorPicker_unstable } from './renderColorPicker'; import { useColorPickerStyles_unstable } from './useColorPickerStyles.styles'; import type { ColorPickerProps } from './ColorPicker.types'; -import { useColorPickerContextValues } from '../../contexts/colorPicker'; +import { useColorPickerContextValues } from './useColorPickerContextValues'; import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts'; /** diff --git a/packages/react-components/react-color-picker/library/src/components/ColorPicker/useColorPickerContextValues.ts b/packages/react-components/react-color-picker/library/src/components/ColorPicker/useColorPickerContextValues.ts new file mode 100644 index 0000000000000..4bcc2c961e741 --- /dev/null +++ b/packages/react-components/react-color-picker/library/src/components/ColorPicker/useColorPickerContextValues.ts @@ -0,0 +1,20 @@ +'use client'; + +import * as React from 'react'; +import type { ColorPickerContextValue, ColorPickerContextValues } from '../../contexts/colorPicker'; +import type { ColorPickerState } from './ColorPicker.types'; + +export const useColorPickerContextValues = (state: ColorPickerState): ColorPickerContextValues => { + const { color, shape, requestChange } = state; + + const colorPicker = React.useMemo( + () => ({ + requestChange, + color, + shape, + }), + [requestChange, color, shape], + ); + + return { colorPicker }; +}; diff --git a/packages/react-components/react-color-picker/library/src/contexts/colorPicker.ts b/packages/react-components/react-color-picker/library/src/contexts/colorPicker.ts index 92607a29460ed..ed722cbd96ef8 100644 --- a/packages/react-components/react-color-picker/library/src/contexts/colorPicker.ts +++ b/packages/react-components/react-color-picker/library/src/contexts/colorPicker.ts @@ -3,7 +3,7 @@ import type * as React from 'react'; import { createContext, useContextSelector } from '@fluentui/react-context-selector'; import type { ContextSelector, Context } from '@fluentui/react-context-selector'; -import type { ColorPickerState, ColorPickerProps } from '../components/ColorPicker/ColorPicker.types'; +import type { ColorPickerProps } from '../components/ColorPicker/ColorPicker.types'; import type { HsvColor } from '../types/color'; /** @@ -19,19 +19,6 @@ export type ColorPickerContextValue = Pick requestChange: (event: React.ChangeEvent, data: { color: HsvColor }) => void; }; -export const useColorPickerContextValues = (state: ColorPickerState): ColorPickerContextValues => { - const { color, shape, requestChange } = state; - - // This context is created with "@fluentui/react-context-selector", these is no sense to memoize it - const colorPicker: ColorPickerContextValue = { - requestChange, - color, - shape, - }; - - return { colorPicker }; -}; - export const colorPickerContextDefaultValue: ColorPickerContextValue = { requestChange: () => { /*noop*/ diff --git a/packages/react-components/react-dialog/library/src/components/Dialog/useDialogContextValues.ts b/packages/react-components/react-dialog/library/src/components/Dialog/useDialogContextValues.ts index a56ed7233ebbc..34208c2db8be0 100644 --- a/packages/react-components/react-dialog/library/src/components/Dialog/useDialogContextValues.ts +++ b/packages/react-components/react-dialog/library/src/components/Dialog/useDialogContextValues.ts @@ -1,3 +1,6 @@ +'use client'; + +import * as React from 'react'; import type { DialogContextValue, DialogSurfaceContextValue } from '../../contexts'; import type { DialogContextValues, DialogState } from './Dialog.types'; @@ -15,22 +18,32 @@ export function useDialogContextValues_unstable(state: DialogState): DialogConte unmountOnClose, } = state; - /** - * This context is created with "@fluentui/react-context-selector", - * there is no sense to memoize it - */ - const dialog: DialogContextValue = { - open, - modalType, - dialogRef, - dialogTitleId, - isNestedDialog, - inertTrapFocus, - modalAttributes, - triggerAttributes, - unmountOnClose, - requestOpenChange, - }; + const dialog = React.useMemo( + () => ({ + open, + modalType, + dialogRef, + dialogTitleId, + isNestedDialog, + inertTrapFocus, + modalAttributes, + triggerAttributes, + unmountOnClose, + requestOpenChange, + }), + [ + open, + modalType, + dialogRef, + dialogTitleId, + isNestedDialog, + inertTrapFocus, + modalAttributes, + triggerAttributes, + unmountOnClose, + requestOpenChange, + ], + ); const dialogSurface: DialogSurfaceContextValue = false; diff --git a/packages/react-components/react-list/library/src/components/List/useListContextValues.ts b/packages/react-components/react-list/library/src/components/List/useListContextValues.ts index 85188f494cfbf..5aae614482464 100644 --- a/packages/react-components/react-list/library/src/components/List/useListContextValues.ts +++ b/packages/react-components/react-list/library/src/components/List/useListContextValues.ts @@ -6,11 +6,14 @@ import type { ListContextValues, ListState } from './List.types'; export function useListContextValues_unstable(state: ListState): ListContextValues { const { selection, navigationMode, listItemRole, validateListItem } = state; - const listContext = { - selection, - navigationMode, - validateListItem, - }; + const listContext = React.useMemo( + () => ({ + selection, + navigationMode, + validateListItem, + }), + [selection, navigationMode, validateListItem], + ); const synchronousContext = React.useMemo( () => ({ diff --git a/packages/react-components/react-menu/library/src/components/Menu/useMenuContextValues.ts b/packages/react-components/react-menu/library/src/components/Menu/useMenuContextValues.ts index 4f5a5fd32b7a7..6ccb60413cadf 100644 --- a/packages/react-components/react-menu/library/src/components/Menu/useMenuContextValues.ts +++ b/packages/react-components/react-menu/library/src/components/Menu/useMenuContextValues.ts @@ -1,3 +1,6 @@ +'use client'; + +import * as React from 'react'; import type { MenuContextValues, MenuState } from './Menu.types'; export function useMenuContextValues_unstable(state: MenuState): MenuContextValues { @@ -20,25 +23,44 @@ export function useMenuContextValues_unstable(state: MenuState): MenuContextValu triggerRef, } = state; - // This context is created with "@fluentui/react-context-selector", these is no sense to memoize it - const menu = { - checkedValues, - hasCheckmarks, - hasIcons, - inline, - isSubmenu, - menuPopoverRef, - mountNode, - onCheckedValueChange, - open, - openOnContext, - openOnHover, - persistOnItemClick, - safeZone, - setOpen, - triggerId, - triggerRef, - }; + const menu = React.useMemo( + () => ({ + checkedValues, + hasCheckmarks, + hasIcons, + inline, + isSubmenu, + menuPopoverRef, + mountNode, + onCheckedValueChange, + open, + openOnContext, + openOnHover, + persistOnItemClick, + safeZone, + setOpen, + triggerId, + triggerRef, + }), + [ + checkedValues, + hasCheckmarks, + hasIcons, + inline, + isSubmenu, + menuPopoverRef, + mountNode, + onCheckedValueChange, + open, + openOnContext, + openOnHover, + persistOnItemClick, + safeZone, + setOpen, + triggerId, + triggerRef, + ], + ); return { menu }; } diff --git a/packages/react-components/react-menu/library/src/components/MenuList/useMenuListContextValues.ts b/packages/react-components/react-menu/library/src/components/MenuList/useMenuListContextValues.ts index 150bc1a7e253b..5e671c856a5d9 100644 --- a/packages/react-components/react-menu/library/src/components/MenuList/useMenuListContextValues.ts +++ b/packages/react-components/react-menu/library/src/components/MenuList/useMenuListContextValues.ts @@ -1,17 +1,22 @@ +'use client'; + +import * as React from 'react'; import type { MenuListContextValues, MenuListState } from './MenuList.types'; export function useMenuListContextValues_unstable(state: MenuListState): MenuListContextValues { const { checkedValues, hasCheckmarks, hasIcons, selectRadio, setFocusByFirstCharacter, toggleCheckbox } = state; - // This context is created with "@fluentui/react-context-selector", these is no sense to memoize it - const menuList = { - checkedValues, - hasCheckmarks, - hasIcons, - selectRadio, - setFocusByFirstCharacter, - toggleCheckbox, - }; + const menuList = React.useMemo( + () => ({ + checkedValues, + hasCheckmarks, + hasIcons, + selectRadio, + setFocusByFirstCharacter, + toggleCheckbox, + }), + [checkedValues, hasCheckmarks, hasIcons, selectRadio, setFocusByFirstCharacter, toggleCheckbox], + ); return { menuList }; } diff --git a/packages/react-components/react-popover/library/etc/react-popover.api.md b/packages/react-components/react-popover/library/etc/react-popover.api.md index 239f1482b6e14..efe6b14680e9b 100644 --- a/packages/react-components/react-popover/library/etc/react-popover.api.md +++ b/packages/react-components/react-popover/library/etc/react-popover.api.md @@ -41,6 +41,11 @@ export const Popover: React_2.FC; // @public export type PopoverContextValue = Pick; +// @public (undocumented) +export type PopoverContextValues = { + popover: PopoverContextValue; +}; + // @public export type PopoverProps = ComponentProps> & Pick & { appearance?: 'brand' | 'inverted'; @@ -125,7 +130,7 @@ export type PopoverTriggerState = { }; // @public -export const renderPopover_unstable: (state: PopoverState) => JSXElement; +export const renderPopover_unstable: (state: PopoverState, contextValues?: PopoverContextValues) => JSXElement; // @public export const renderPopoverSurface_unstable: (state: PopoverSurfaceState) => JSXElement; @@ -139,6 +144,9 @@ export const usePopover_unstable: (props: PopoverProps) => PopoverState; // @public (undocumented) export const usePopoverContext_unstable: (selector: ContextSelector) => T; +// @public (undocumented) +export function usePopoverContextValues_unstable(state: PopoverState): PopoverContextValues; + // @public export const usePopoverSurface_unstable: (props: PopoverSurfaceProps, ref: React_2.Ref) => PopoverSurfaceState; diff --git a/packages/react-components/react-popover/library/src/Popover.ts b/packages/react-components/react-popover/library/src/Popover.ts index e1a3fece22193..5e9a94a544350 100644 --- a/packages/react-components/react-popover/library/src/Popover.ts +++ b/packages/react-components/react-popover/library/src/Popover.ts @@ -2,6 +2,7 @@ export type { OnOpenChangeData, OpenPopoverEvents, PopoverBaseProps, + PopoverContextValues, PopoverProps, PopoverSize, PopoverBaseState, @@ -12,4 +13,5 @@ export { renderPopover_unstable, usePopover_unstable, usePopoverBase_unstable, + usePopoverContextValues_unstable, } from './components/Popover/index'; diff --git a/packages/react-components/react-popover/library/src/components/Popover/Popover.tsx b/packages/react-components/react-popover/library/src/components/Popover/Popover.tsx index 3d06ac2d51da5..bcc5922ecc578 100644 --- a/packages/react-components/react-popover/library/src/components/Popover/Popover.tsx +++ b/packages/react-components/react-popover/library/src/components/Popover/Popover.tsx @@ -2,6 +2,7 @@ import type * as React from 'react'; import { usePopover_unstable } from './usePopover'; +import { usePopoverContextValues_unstable } from './usePopoverContextValues'; import { renderPopover_unstable } from './renderPopover'; import type { PopoverProps } from './Popover.types'; @@ -10,8 +11,9 @@ import type { PopoverProps } from './Popover.types'; */ export const Popover: React.FC = props => { const state = usePopover_unstable(props); + const contextValues = usePopoverContextValues_unstable(state); - return renderPopover_unstable(state); + return renderPopover_unstable(state, contextValues); }; Popover.displayName = 'Popover'; diff --git a/packages/react-components/react-popover/library/src/components/Popover/index.ts b/packages/react-components/react-popover/library/src/components/Popover/index.ts index c8f1730b130a4..b2e41dd5b8ec2 100644 --- a/packages/react-components/react-popover/library/src/components/Popover/index.ts +++ b/packages/react-components/react-popover/library/src/components/Popover/index.ts @@ -10,3 +10,5 @@ export type { } from './Popover.types'; export { renderPopover_unstable } from './renderPopover'; export { usePopover_unstable, usePopoverBase_unstable } from './usePopover'; +export { usePopoverContextValues_unstable } from './usePopoverContextValues'; +export type { PopoverContextValues } from './usePopoverContextValues'; diff --git a/packages/react-components/react-popover/library/src/components/Popover/renderPopover.tsx b/packages/react-components/react-popover/library/src/components/Popover/renderPopover.tsx index 633c9e4762c42..3dec44e95bfdb 100644 --- a/packages/react-components/react-popover/library/src/components/Popover/renderPopover.tsx +++ b/packages/react-components/react-popover/library/src/components/Popover/renderPopover.tsx @@ -4,53 +4,18 @@ import * as React from 'react'; import { assertSlots, type JSXElement } from '@fluentui/react-utilities'; import { MotionRefForwarder } from '@fluentui/react-motion'; -import { PopoverContext } from '../../popoverContext'; +import { PopoverContext, popoverContextDefaultValue } from '../../popoverContext'; import type { InternalPopoverSlots, PopoverState } from './Popover.types'; +import type { PopoverContextValues } from './usePopoverContextValues'; /** * Render the final JSX of Popover */ -export const renderPopover_unstable = (state: PopoverState): JSXElement => { +export const renderPopover_unstable = (state: PopoverState, contextValues?: PopoverContextValues): JSXElement => { assertSlots(state); - const { - appearance, - arrowRef, - contentRef, - inline, - mountNode, - open, - openOnContext, - openOnHover, - setOpen, - size, - toggleOpen, - trapFocus, - triggerRef, - withArrow, - inertTrapFocus, - } = state; - return ( - + {state.popoverTrigger} {state.popoverSurface && ( diff --git a/packages/react-components/react-popover/library/src/components/Popover/usePopoverContextValues.ts b/packages/react-components/react-popover/library/src/components/Popover/usePopoverContextValues.ts new file mode 100644 index 0000000000000..b31d0ad1231fa --- /dev/null +++ b/packages/react-components/react-popover/library/src/components/Popover/usePopoverContextValues.ts @@ -0,0 +1,68 @@ +'use client'; + +import * as React from 'react'; +import type { PopoverContextValue } from '../../popoverContext'; +import type { PopoverState } from './Popover.types'; + +export type PopoverContextValues = { + popover: PopoverContextValue; +}; + +export function usePopoverContextValues_unstable(state: PopoverState): PopoverContextValues { + const { + appearance, + arrowRef, + contentRef, + inline, + mountNode, + open, + openOnContext, + openOnHover, + setOpen, + size, + toggleOpen, + trapFocus, + triggerRef, + withArrow, + inertTrapFocus, + } = state; + + const popover = React.useMemo( + () => ({ + appearance, + arrowRef, + contentRef, + inline, + mountNode, + open, + openOnContext, + openOnHover, + setOpen, + size, + toggleOpen, + trapFocus, + triggerRef, + withArrow, + inertTrapFocus, + }), + [ + appearance, + arrowRef, + contentRef, + inline, + mountNode, + open, + openOnContext, + openOnHover, + setOpen, + size, + toggleOpen, + trapFocus, + triggerRef, + withArrow, + inertTrapFocus, + ], + ); + + return { popover }; +} diff --git a/packages/react-components/react-popover/library/src/index.ts b/packages/react-components/react-popover/library/src/index.ts index 7179d31aaa712..0f5164b8bd37b 100644 --- a/packages/react-components/react-popover/library/src/index.ts +++ b/packages/react-components/react-popover/library/src/index.ts @@ -1,5 +1,12 @@ -export { Popover, renderPopover_unstable, usePopover_unstable } from './Popover'; -export type { OnOpenChangeData, OpenPopoverEvents, PopoverProps, PopoverSize, PopoverState } from './Popover'; +export { Popover, renderPopover_unstable, usePopover_unstable, usePopoverContextValues_unstable } from './Popover'; +export type { + OnOpenChangeData, + OpenPopoverEvents, + PopoverContextValues, + PopoverProps, + PopoverSize, + PopoverState, +} from './Popover'; export { PopoverSurface, arrowHeights, diff --git a/packages/react-components/react-popover/library/src/popoverContext.ts b/packages/react-components/react-popover/library/src/popoverContext.ts index 6ca8a6ae910f6..ea854a95ea53b 100644 --- a/packages/react-components/react-popover/library/src/popoverContext.ts +++ b/packages/react-components/react-popover/library/src/popoverContext.ts @@ -7,7 +7,7 @@ import type { PopoverState } from './components/Popover/index'; export const PopoverContext: Context = createContext( undefined, ) as Context; -const popoverContextDefaultValue: PopoverContextValue = { +export const popoverContextDefaultValue: PopoverContextValue = { open: false, setOpen: () => null, toggleOpen: () => null, diff --git a/packages/react-components/react-swatch-picker/library/src/components/SwatchPicker/SwatchPicker.tsx b/packages/react-components/react-swatch-picker/library/src/components/SwatchPicker/SwatchPicker.tsx index 482bc15c6bfb2..30723ac759783 100644 --- a/packages/react-components/react-swatch-picker/library/src/components/SwatchPicker/SwatchPicker.tsx +++ b/packages/react-components/react-swatch-picker/library/src/components/SwatchPicker/SwatchPicker.tsx @@ -6,7 +6,7 @@ import { useSwatchPicker_unstable } from './useSwatchPicker'; import { renderSwatchPicker_unstable } from './renderSwatchPicker'; import { useSwatchPickerStyles_unstable } from './useSwatchPickerStyles.styles'; import type { SwatchPickerProps } from './SwatchPicker.types'; -import { useSwatchPickerContextValues } from '../../contexts/swatchPicker'; +import { useSwatchPickerContextValues } from './useSwatchPickerContextValues'; import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts'; /** diff --git a/packages/react-components/react-swatch-picker/library/src/components/SwatchPicker/useSwatchPickerContextValues.ts b/packages/react-components/react-swatch-picker/library/src/components/SwatchPicker/useSwatchPickerContextValues.ts new file mode 100644 index 0000000000000..468e13c6525fa --- /dev/null +++ b/packages/react-components/react-swatch-picker/library/src/components/SwatchPicker/useSwatchPickerContextValues.ts @@ -0,0 +1,23 @@ +'use client'; + +import * as React from 'react'; +import type { SwatchPickerContextValue, SwatchPickerContextValues } from '../../contexts/swatchPicker'; +import type { SwatchPickerState } from './SwatchPicker.types'; + +export const useSwatchPickerContextValues = (state: SwatchPickerState): SwatchPickerContextValues => { + const { isGrid, size, shape, spacing, requestSelectionChange, selectedValue } = state; + + const swatchPicker = React.useMemo( + () => ({ + isGrid, + size, + shape, + spacing, + selectedValue, + requestSelectionChange, + }), + [isGrid, size, shape, spacing, selectedValue, requestSelectionChange], + ); + + return { swatchPicker }; +}; diff --git a/packages/react-components/react-swatch-picker/library/src/contexts/index.ts b/packages/react-components/react-swatch-picker/library/src/contexts/index.ts index 7f8d24738f84d..e13d43318dcec 100644 --- a/packages/react-components/react-swatch-picker/library/src/contexts/index.ts +++ b/packages/react-components/react-swatch-picker/library/src/contexts/index.ts @@ -3,5 +3,5 @@ export { SwatchPickerProvider, swatchPickerContextDefaultValue, useSwatchPickerContextValue_unstable, - useSwatchPickerContextValues, } from './swatchPicker'; +export { useSwatchPickerContextValues } from '../components/SwatchPicker/useSwatchPickerContextValues'; diff --git a/packages/react-components/react-swatch-picker/library/src/contexts/swatchPicker.ts b/packages/react-components/react-swatch-picker/library/src/contexts/swatchPicker.ts index ccaebc1d663b4..fc26413710c2b 100644 --- a/packages/react-components/react-swatch-picker/library/src/contexts/swatchPicker.ts +++ b/packages/react-components/react-swatch-picker/library/src/contexts/swatchPicker.ts @@ -3,7 +3,7 @@ import type * as React from 'react'; import { createContext, useContextSelector } from '@fluentui/react-context-selector'; import type { ContextSelector, Context } from '@fluentui/react-context-selector'; -import type { SwatchPickerProps, SwatchPickerState } from '../components/SwatchPicker/SwatchPicker.types'; +import type { SwatchPickerProps } from '../components/SwatchPicker/SwatchPicker.types'; /** * The context through which individual color controls communicate with the picker. @@ -26,22 +26,6 @@ export type SwatchPickerContextValue = Pick void; }; -export const useSwatchPickerContextValues = (state: SwatchPickerState): SwatchPickerContextValues => { - const { isGrid, size, shape, spacing, requestSelectionChange, selectedValue } = state; - - // This context is created with "@fluentui/react-context-selector", these is no sense to memoize it - const swatchPicker: SwatchPickerContextValue = { - isGrid, - size, - shape, - spacing, - selectedValue, - requestSelectionChange, - }; - - return { swatchPicker }; -}; - export const swatchPickerContextDefaultValue: SwatchPickerContextValue = { requestSelectionChange: () => { /*noop*/ diff --git a/packages/react-components/react-table/library/src/components/DataGrid/useDataGridContextValues.ts b/packages/react-components/react-table/library/src/components/DataGrid/useDataGridContextValues.ts index 8e62c68e4e0cf..edb9a9ed2fab2 100644 --- a/packages/react-components/react-table/library/src/components/DataGrid/useDataGridContextValues.ts +++ b/packages/react-components/react-table/library/src/components/DataGrid/useDataGridContextValues.ts @@ -1,5 +1,6 @@ 'use client'; +import * as React from 'react'; import { useTableContextValues_unstable } from '../Table/useTableContextValues'; import type { DataGridContextValues, DataGridState } from './DataGrid.types'; @@ -14,9 +15,9 @@ export function useDataGridContextValues_unstable(state: DataGridState): DataGri resizableColumns, compositeRowTabsterAttribute, } = state; - return { - ...tableContextValues, - dataGrid: { + + const dataGrid = React.useMemo( + () => ({ ...tableState, focusMode, selectableRows, @@ -24,6 +25,20 @@ export function useDataGridContextValues_unstable(state: DataGridState): DataGri selectionAppearance, resizableColumns, compositeRowTabsterAttribute, - }, + }), + [ + tableState, + focusMode, + selectableRows, + subtleSelection, + selectionAppearance, + resizableColumns, + compositeRowTabsterAttribute, + ], + ); + + return { + ...tableContextValues, + dataGrid, }; } diff --git a/packages/react-components/react-tabs/library/src/components/TabList/useTabListContextValues.tsx b/packages/react-components/react-tabs/library/src/components/TabList/useTabListContextValues.tsx index 22a463f621c05..89ab5f2e38125 100644 --- a/packages/react-components/react-tabs/library/src/components/TabList/useTabListContextValues.tsx +++ b/packages/react-components/react-tabs/library/src/components/TabList/useTabListContextValues.tsx @@ -1,3 +1,6 @@ +'use client'; + +import * as React from 'react'; import type { TabListContextValue, TabListContextValues, TabListState } from './TabList.types'; export function useTabListContextValues_unstable(state: TabListState): TabListContextValues { @@ -15,19 +18,34 @@ export function useTabListContextValues_unstable(state: TabListState): TabListCo vertical, } = state; - const tabList: TabListContextValue = { - appearance, - reserveSelectedTabSpace, - disabled, - selectTabOnFocus, - selectedValue: selectedKey, - onSelect, - onRegister, - onUnregister, - getRegisteredTabs, - size, - vertical, - }; + const tabList = React.useMemo( + () => ({ + appearance, + reserveSelectedTabSpace, + disabled, + selectTabOnFocus, + selectedValue: selectedKey, + onSelect, + onRegister, + onUnregister, + getRegisteredTabs, + size, + vertical, + }), + [ + appearance, + reserveSelectedTabSpace, + disabled, + selectTabOnFocus, + selectedKey, + onSelect, + onRegister, + onUnregister, + getRegisteredTabs, + size, + vertical, + ], + ); return { tabList }; } diff --git a/packages/react-components/react-teaching-popover/library/etc/react-teaching-popover.api.md b/packages/react-components/react-teaching-popover/library/etc/react-teaching-popover.api.md index 2a80440f86a17..aea9439052959 100644 --- a/packages/react-components/react-teaching-popover/library/etc/react-teaching-popover.api.md +++ b/packages/react-components/react-teaching-popover/library/etc/react-teaching-popover.api.md @@ -15,6 +15,7 @@ import type { EventHandler } from '@fluentui/react-utilities'; import type { ForwardRefComponent } from '@fluentui/react-utilities'; import { JSXElement } from '@fluentui/react-utilities'; import { PopoverContextValue } from '@fluentui/react-popover'; +import { PopoverContextValues } from '@fluentui/react-popover'; import type { PopoverProps } from '@fluentui/react-popover'; import { PopoverState } from '@fluentui/react-popover'; import type { PopoverSurfaceSlots } from '@fluentui/react-popover'; @@ -27,7 +28,7 @@ import type { Slot } from '@fluentui/react-utilities'; import type { SlotClassNames } from '@fluentui/react-utilities'; // @public -export const renderTeachingPopover_unstable: (state: PopoverState) => JSXElement; +export const renderTeachingPopover_unstable: (state: PopoverState, contextValues?: PopoverContextValues) => JSXElement; // @public export const renderTeachingPopoverBody_unstable: (state: TeachingPopoverBodyState) => JSXElement; diff --git a/packages/react-components/react-teaching-popover/library/src/components/TeachingPopoverCarousel/useTeachingPopoverCarouselContextValues.ts b/packages/react-components/react-teaching-popover/library/src/components/TeachingPopoverCarousel/useTeachingPopoverCarouselContextValues.ts index 19e2a489b4075..a215206fe4ade 100644 --- a/packages/react-components/react-teaching-popover/library/src/components/TeachingPopoverCarousel/useTeachingPopoverCarouselContextValues.ts +++ b/packages/react-components/react-teaching-popover/library/src/components/TeachingPopoverCarousel/useTeachingPopoverCarouselContextValues.ts @@ -1,3 +1,6 @@ +'use client'; + +import * as React from 'react'; import type { TeachingPopoverCarouselContextValues, TeachingPopoverCarouselState, @@ -8,12 +11,15 @@ export function useTeachingPopoverCarouselContextValues_unstable( ): TeachingPopoverCarouselContextValues { const { store, value, selectPageByValue, selectPageByDirection } = state; - const carousel = { - store, - value, - selectPageByDirection, - selectPageByValue, - }; + const carousel = React.useMemo( + () => ({ + store, + value, + selectPageByDirection, + selectPageByValue, + }), + [store, value, selectPageByDirection, selectPageByValue], + ); return { carousel }; } diff --git a/packages/react-components/react-toolbar/library/src/components/Toolbar/useToolbarContextValues.tsx b/packages/react-components/react-toolbar/library/src/components/Toolbar/useToolbarContextValues.tsx index 7426e78e1af82..bceb0a22b69db 100644 --- a/packages/react-components/react-toolbar/library/src/components/Toolbar/useToolbarContextValues.tsx +++ b/packages/react-components/react-toolbar/library/src/components/Toolbar/useToolbarContextValues.tsx @@ -1,15 +1,21 @@ +'use client'; + +import * as React from 'react'; import type { ToolbarContextValue, ToolbarContextValues, ToolbarState } from './Toolbar.types'; export function useToolbarContextValues_unstable(state: ToolbarState): ToolbarContextValues { const { size, handleToggleButton, vertical, checkedValues, handleRadio } = state; - // This context is created with "@fluentui/react-context-selector", these is no sense to memoize it - const toolbar: ToolbarContextValue = { - size, - vertical, - handleToggleButton, - handleRadio, - checkedValues, - }; + + const toolbar = React.useMemo( + () => ({ + size, + vertical, + handleToggleButton, + handleRadio, + checkedValues, + }), + [size, vertical, handleToggleButton, handleRadio, checkedValues], + ); return { toolbar }; } diff --git a/packages/react-components/react-tree/library/src/components/FlatTree/useFlatTreeContextValues.ts b/packages/react-components/react-tree/library/src/components/FlatTree/useFlatTreeContextValues.ts index 9c30a476081ed..fbda582a0d1f1 100644 --- a/packages/react-components/react-tree/library/src/components/FlatTree/useFlatTreeContextValues.ts +++ b/packages/react-components/react-tree/library/src/components/FlatTree/useFlatTreeContextValues.ts @@ -1,3 +1,6 @@ +'use client'; + +import * as React from 'react'; import type { TreeContextValue } from '../../contexts'; import type { FlatTreeContextValues, FlatTreeState } from './FlatTree.types'; @@ -15,23 +18,35 @@ export const useFlatTreeContextValues_unstable = (state: FlatTreeState): FlatTre requestTreeResponse, forceUpdateRovingTabIndex, } = state; - /** - * This context is created with "@fluentui/react-context-selector", - * there is no sense to memoize it - */ - const tree: TreeContextValue = { - treeType, - size, - openItems, - appearance, - checkedItems, - selectionMode, - navigationMode, - contextType, - level, - requestTreeResponse, - forceUpdateRovingTabIndex, - }; + + const tree = React.useMemo( + () => ({ + treeType, + size, + openItems, + appearance, + checkedItems, + selectionMode, + navigationMode, + contextType, + level, + requestTreeResponse, + forceUpdateRovingTabIndex, + }), + [ + treeType, + size, + openItems, + appearance, + checkedItems, + selectionMode, + navigationMode, + contextType, + level, + requestTreeResponse, + forceUpdateRovingTabIndex, + ], + ); return { tree }; }; diff --git a/packages/react-components/react-tree/library/src/components/Tree/useTreeContextValues.ts b/packages/react-components/react-tree/library/src/components/Tree/useTreeContextValues.ts index 8f486ce304781..bd9b1f4c3536c 100644 --- a/packages/react-components/react-tree/library/src/components/Tree/useTreeContextValues.ts +++ b/packages/react-components/react-tree/library/src/components/Tree/useTreeContextValues.ts @@ -5,8 +5,6 @@ import type { TreeContextValue } from '../../contexts'; import type { TreeContextValues, TreeState } from './Tree.types'; export function useTreeContextValues_unstable(state: TreeState): TreeContextValues { - 'use no memo'; - if (state.contextType === 'root') { const { openItems, @@ -21,23 +19,36 @@ export function useTreeContextValues_unstable(state: TreeState): TreeContextValu requestTreeResponse, forceUpdateRovingTabIndex, } = state; - /** - * This context is created with "@fluentui/react-context-selector", - * there is no sense to memoize it - */ - const tree: TreeContextValue = { - treeType, - size, - openItems, - appearance, - checkedItems, - selectionMode, - navigationMode, - contextType, - level, - requestTreeResponse, - forceUpdateRovingTabIndex, - }; + // contextType is statically determined by the context + // eslint-disable-next-line react-hooks/rules-of-hooks + const tree = React.useMemo( + () => ({ + treeType, + size, + openItems, + appearance, + checkedItems, + selectionMode, + navigationMode, + contextType, + level, + requestTreeResponse, + forceUpdateRovingTabIndex, + }), + [ + treeType, + size, + openItems, + appearance, + checkedItems, + selectionMode, + navigationMode, + contextType, + level, + requestTreeResponse, + forceUpdateRovingTabIndex, + ], + ); return { tree }; } diff --git a/packages/react-components/react-tree/library/src/components/TreeItem/useTreeItemContextValues.ts b/packages/react-components/react-tree/library/src/components/TreeItem/useTreeItemContextValues.ts index fc200e42d7685..4442b1e9c5cc1 100644 --- a/packages/react-components/react-tree/library/src/components/TreeItem/useTreeItemContextValues.ts +++ b/packages/react-components/react-tree/library/src/components/TreeItem/useTreeItemContextValues.ts @@ -1,3 +1,6 @@ +'use client'; + +import * as React from 'react'; import type { TreeItemContextValues, TreeItemState } from './TreeItem.types'; import type { TreeItemContextValue } from '../../contexts'; @@ -19,24 +22,36 @@ export function useTreeItemContextValues_unstable(state: TreeItemState): TreeIte checked, } = state; - /** - * This context is created with "@fluentui/react-context-selector", - * there is no sense to memoize it - */ - const treeItem: TreeItemContextValue = { - value, - checked, - itemType, - layoutRef, - subtreeRef, - open, - selectionRef, - isActionsVisible, - isAsideVisible, - actionsRef, - treeItemRef, - expandIconRef, - }; + const treeItem = React.useMemo( + () => ({ + value, + checked, + itemType, + layoutRef, + subtreeRef, + open, + selectionRef, + isActionsVisible, + isAsideVisible, + actionsRef, + treeItemRef, + expandIconRef, + }), + [ + value, + checked, + itemType, + layoutRef, + subtreeRef, + open, + selectionRef, + isActionsVisible, + isAsideVisible, + actionsRef, + treeItemRef, + expandIconRef, + ], + ); return { treeItem }; } From dd34c6275ad515c20adc1323b1adc730172d172a Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Mon, 20 Apr 2026 11:57:09 +0200 Subject: [PATCH 2/3] restore annotation --- ...act-accordion-a035aacb-feec-4686-9d08-dfe5b99b113f.json | 7 +++++++ ...-react-avatar-a7ba6b80-bd13-41cd-9f87-19a821ed169f.json | 7 +++++++ ...eact-carousel-644cda54-062a-43b9-981f-c1fabf97482c.json | 7 +++++++ ...-color-picker-d144aa96-5c6a-43be-9c90-21d1bd2486f9.json | 7 +++++++ ...-react-dialog-6b927bc7-5fbf-453a-b6b3-618f33f0cef4.json | 7 +++++++ ...ui-react-list-f2d5fd86-ac00-4a26-827d-1e8735fd408e.json | 7 +++++++ ...ui-react-menu-b15954ad-9104-4f6b-836d-a0b19c2aa079.json | 7 +++++++ ...react-popover-6dcad78f-0802-4813-9338-396aa1c76c54.json | 7 +++++++ ...swatch-picker-62d6355e-f4aa-46f8-a3fc-a0b47b7b19ff.json | 7 +++++++ ...i-react-table-971a0a61-873b-490c-8aa6-55525810b3ef.json | 7 +++++++ ...ui-react-tabs-2788d005-7db8-4940-b1b2-8555f2856eb3.json | 7 +++++++ ...ching-popover-0fb348ae-dd55-4ad2-9c67-e07659802fae.json | 7 +++++++ ...react-toolbar-4958f82a-f2a1-4c90-a617-3072254072bc.json | 7 +++++++ ...ui-react-tree-a8645cae-35d2-4d3d-931e-1e0b361e63ad.json | 7 +++++++ .../library/src/components/Tree/useTreeContextValues.ts | 2 ++ 15 files changed, 100 insertions(+) create mode 100644 change/@fluentui-react-accordion-a035aacb-feec-4686-9d08-dfe5b99b113f.json create mode 100644 change/@fluentui-react-avatar-a7ba6b80-bd13-41cd-9f87-19a821ed169f.json create mode 100644 change/@fluentui-react-carousel-644cda54-062a-43b9-981f-c1fabf97482c.json create mode 100644 change/@fluentui-react-color-picker-d144aa96-5c6a-43be-9c90-21d1bd2486f9.json create mode 100644 change/@fluentui-react-dialog-6b927bc7-5fbf-453a-b6b3-618f33f0cef4.json create mode 100644 change/@fluentui-react-list-f2d5fd86-ac00-4a26-827d-1e8735fd408e.json create mode 100644 change/@fluentui-react-menu-b15954ad-9104-4f6b-836d-a0b19c2aa079.json create mode 100644 change/@fluentui-react-popover-6dcad78f-0802-4813-9338-396aa1c76c54.json create mode 100644 change/@fluentui-react-swatch-picker-62d6355e-f4aa-46f8-a3fc-a0b47b7b19ff.json create mode 100644 change/@fluentui-react-table-971a0a61-873b-490c-8aa6-55525810b3ef.json create mode 100644 change/@fluentui-react-tabs-2788d005-7db8-4940-b1b2-8555f2856eb3.json create mode 100644 change/@fluentui-react-teaching-popover-0fb348ae-dd55-4ad2-9c67-e07659802fae.json create mode 100644 change/@fluentui-react-toolbar-4958f82a-f2a1-4c90-a617-3072254072bc.json create mode 100644 change/@fluentui-react-tree-a8645cae-35d2-4d3d-931e-1e0b361e63ad.json diff --git a/change/@fluentui-react-accordion-a035aacb-feec-4686-9d08-dfe5b99b113f.json b/change/@fluentui-react-accordion-a035aacb-feec-4686-9d08-dfe5b99b113f.json new file mode 100644 index 0000000000000..58a4ec39c12ea --- /dev/null +++ b/change/@fluentui-react-accordion-a035aacb-feec-4686-9d08-dfe5b99b113f.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-accordion", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-avatar-a7ba6b80-bd13-41cd-9f87-19a821ed169f.json b/change/@fluentui-react-avatar-a7ba6b80-bd13-41cd-9f87-19a821ed169f.json new file mode 100644 index 0000000000000..b5f92cd98c141 --- /dev/null +++ b/change/@fluentui-react-avatar-a7ba6b80-bd13-41cd-9f87-19a821ed169f.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-avatar", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-carousel-644cda54-062a-43b9-981f-c1fabf97482c.json b/change/@fluentui-react-carousel-644cda54-062a-43b9-981f-c1fabf97482c.json new file mode 100644 index 0000000000000..7b20b904140c6 --- /dev/null +++ b/change/@fluentui-react-carousel-644cda54-062a-43b9-981f-c1fabf97482c.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-carousel", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-color-picker-d144aa96-5c6a-43be-9c90-21d1bd2486f9.json b/change/@fluentui-react-color-picker-d144aa96-5c6a-43be-9c90-21d1bd2486f9.json new file mode 100644 index 0000000000000..57de4c5648b80 --- /dev/null +++ b/change/@fluentui-react-color-picker-d144aa96-5c6a-43be-9c90-21d1bd2486f9.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-color-picker", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-dialog-6b927bc7-5fbf-453a-b6b3-618f33f0cef4.json b/change/@fluentui-react-dialog-6b927bc7-5fbf-453a-b6b3-618f33f0cef4.json new file mode 100644 index 0000000000000..f409f5d9b4e7c --- /dev/null +++ b/change/@fluentui-react-dialog-6b927bc7-5fbf-453a-b6b3-618f33f0cef4.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-dialog", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-list-f2d5fd86-ac00-4a26-827d-1e8735fd408e.json b/change/@fluentui-react-list-f2d5fd86-ac00-4a26-827d-1e8735fd408e.json new file mode 100644 index 0000000000000..5019cae5c248c --- /dev/null +++ b/change/@fluentui-react-list-f2d5fd86-ac00-4a26-827d-1e8735fd408e.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-list", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-menu-b15954ad-9104-4f6b-836d-a0b19c2aa079.json b/change/@fluentui-react-menu-b15954ad-9104-4f6b-836d-a0b19c2aa079.json new file mode 100644 index 0000000000000..3f3d8e4d9c296 --- /dev/null +++ b/change/@fluentui-react-menu-b15954ad-9104-4f6b-836d-a0b19c2aa079.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-menu", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-popover-6dcad78f-0802-4813-9338-396aa1c76c54.json b/change/@fluentui-react-popover-6dcad78f-0802-4813-9338-396aa1c76c54.json new file mode 100644 index 0000000000000..e1ecbc0aaf827 --- /dev/null +++ b/change/@fluentui-react-popover-6dcad78f-0802-4813-9338-396aa1c76c54.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-popover", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-swatch-picker-62d6355e-f4aa-46f8-a3fc-a0b47b7b19ff.json b/change/@fluentui-react-swatch-picker-62d6355e-f4aa-46f8-a3fc-a0b47b7b19ff.json new file mode 100644 index 0000000000000..a21d6b976b293 --- /dev/null +++ b/change/@fluentui-react-swatch-picker-62d6355e-f4aa-46f8-a3fc-a0b47b7b19ff.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-swatch-picker", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-table-971a0a61-873b-490c-8aa6-55525810b3ef.json b/change/@fluentui-react-table-971a0a61-873b-490c-8aa6-55525810b3ef.json new file mode 100644 index 0000000000000..739f3f4b72513 --- /dev/null +++ b/change/@fluentui-react-table-971a0a61-873b-490c-8aa6-55525810b3ef.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-table", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-tabs-2788d005-7db8-4940-b1b2-8555f2856eb3.json b/change/@fluentui-react-tabs-2788d005-7db8-4940-b1b2-8555f2856eb3.json new file mode 100644 index 0000000000000..052a32a791003 --- /dev/null +++ b/change/@fluentui-react-tabs-2788d005-7db8-4940-b1b2-8555f2856eb3.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-tabs", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-teaching-popover-0fb348ae-dd55-4ad2-9c67-e07659802fae.json b/change/@fluentui-react-teaching-popover-0fb348ae-dd55-4ad2-9c67-e07659802fae.json new file mode 100644 index 0000000000000..202715fc243b2 --- /dev/null +++ b/change/@fluentui-react-teaching-popover-0fb348ae-dd55-4ad2-9c67-e07659802fae.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-teaching-popover", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-toolbar-4958f82a-f2a1-4c90-a617-3072254072bc.json b/change/@fluentui-react-toolbar-4958f82a-f2a1-4c90-a617-3072254072bc.json new file mode 100644 index 0000000000000..19fd109b61cef --- /dev/null +++ b/change/@fluentui-react-toolbar-4958f82a-f2a1-4c90-a617-3072254072bc.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-toolbar", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-tree-a8645cae-35d2-4d3d-931e-1e0b361e63ad.json b/change/@fluentui-react-tree-a8645cae-35d2-4d3d-931e-1e0b361e63ad.json new file mode 100644 index 0000000000000..50495c221ab93 --- /dev/null +++ b/change/@fluentui-react-tree-a8645cae-35d2-4d3d-931e-1e0b361e63ad.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "perf: memoize context values in use*ContextValues hooks", + "packageName": "@fluentui/react-tree", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-tree/library/src/components/Tree/useTreeContextValues.ts b/packages/react-components/react-tree/library/src/components/Tree/useTreeContextValues.ts index bd9b1f4c3536c..4e8a7ed39873a 100644 --- a/packages/react-components/react-tree/library/src/components/Tree/useTreeContextValues.ts +++ b/packages/react-components/react-tree/library/src/components/Tree/useTreeContextValues.ts @@ -5,6 +5,8 @@ import type { TreeContextValue } from '../../contexts'; import type { TreeContextValues, TreeState } from './Tree.types'; export function useTreeContextValues_unstable(state: TreeState): TreeContextValues { + 'use no memo'; + if (state.contextType === 'root') { const { openItems, From ce3566ab856710c7746f9cb40f0607666976445d Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Wed, 22 Apr 2026 10:39:01 +0200 Subject: [PATCH 3/3] fix teaching popover --- .../library/etc/react-teaching-popover.api.md | 3 +++ .../react-teaching-popover/library/src/TeachingPopover.ts | 1 + .../src/components/TeachingPopover/TeachingPopover.tsx | 4 +++- .../components/TeachingPopover/TeachingPopover.types.ts | 4 +++- .../library/src/components/TeachingPopover/index.ts | 1 + .../TeachingPopover/useTeachingPopoverContextValues.ts | 7 +++++++ .../react-teaching-popover/library/src/index.ts | 7 ++++++- 7 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/useTeachingPopoverContextValues.ts diff --git a/packages/react-components/react-teaching-popover/library/etc/react-teaching-popover.api.md b/packages/react-components/react-teaching-popover/library/etc/react-teaching-popover.api.md index aea9439052959..f5175681cef30 100644 --- a/packages/react-components/react-teaching-popover/library/etc/react-teaching-popover.api.md +++ b/packages/react-components/react-teaching-popover/library/etc/react-teaching-popover.api.md @@ -378,6 +378,9 @@ export const useTeachingPopoverCarouselPageCountStyles_unstable: (state: Teachin // @public export const useTeachingPopoverCarouselStyles_unstable: (state: TeachingPopoverCarouselState) => TeachingPopoverCarouselState; +// @public (undocumented) +export const useTeachingPopoverContextValues_unstable: (state: TeachingPopoverState) => TeachingPopoverContextValues; + // @public export const useTeachingPopoverFooter_unstable: (props: TeachingPopoverFooterProps, ref: React_2.Ref) => TeachingPopoverFooterState; diff --git a/packages/react-components/react-teaching-popover/library/src/TeachingPopover.ts b/packages/react-components/react-teaching-popover/library/src/TeachingPopover.ts index c5e4845d52e4b..f5fa30e878e2c 100644 --- a/packages/react-components/react-teaching-popover/library/src/TeachingPopover.ts +++ b/packages/react-components/react-teaching-popover/library/src/TeachingPopover.ts @@ -3,4 +3,5 @@ export { TeachingPopover, renderTeachingPopover_unstable, useTeachingPopover_unstable, + useTeachingPopoverContextValues_unstable, } from './components/TeachingPopover/index'; diff --git a/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/TeachingPopover.tsx b/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/TeachingPopover.tsx index 2d94ae547b3d8..4d40182e079bb 100644 --- a/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/TeachingPopover.tsx +++ b/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/TeachingPopover.tsx @@ -4,14 +4,16 @@ import type * as React from 'react'; import { useTeachingPopover_unstable } from './useTeachingPopover'; import { renderTeachingPopover_unstable } from './renderTeachingPopover'; import type { TeachingPopoverProps } from './TeachingPopover.types'; +import { useTeachingPopoverContextValues_unstable } from './useTeachingPopoverContextValues'; /** * An extension class of Popover which defaults to withArrow and FocusTrap enabled. */ export const TeachingPopover: React.FC = props => { const state = useTeachingPopover_unstable(props); + const contextValues = useTeachingPopoverContextValues_unstable(state); - return renderTeachingPopover_unstable(state); + return renderTeachingPopover_unstable(state, contextValues); }; TeachingPopover.displayName = 'TeachingPopover'; diff --git a/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/TeachingPopover.types.ts b/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/TeachingPopover.types.ts index 409fbbda9fc2d..3e713ff2e8757 100644 --- a/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/TeachingPopover.types.ts +++ b/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/TeachingPopover.types.ts @@ -1,4 +1,4 @@ -import type { PopoverState, PopoverProps } from '@fluentui/react-popover'; +import type { PopoverState, PopoverProps, PopoverContextValues } from '@fluentui/react-popover'; /** * TeachingPopover Props @@ -9,3 +9,5 @@ export type TeachingPopoverProps = PopoverProps; * TeachingPopover State */ export type TeachingPopoverState = PopoverState; + +export type TeachingPopoverContextValues = PopoverContextValues; diff --git a/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/index.ts b/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/index.ts index a881fac1d0b88..a9739da150e8c 100644 --- a/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/index.ts +++ b/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/index.ts @@ -2,3 +2,4 @@ export { TeachingPopover } from './TeachingPopover'; export type { TeachingPopoverProps, TeachingPopoverState } from './TeachingPopover.types'; export { renderTeachingPopover_unstable } from './renderTeachingPopover'; export { useTeachingPopover_unstable } from './useTeachingPopover'; +export { useTeachingPopoverContextValues_unstable } from './useTeachingPopoverContextValues'; diff --git a/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/useTeachingPopoverContextValues.ts b/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/useTeachingPopoverContextValues.ts new file mode 100644 index 0000000000000..7ae99bed93861 --- /dev/null +++ b/packages/react-components/react-teaching-popover/library/src/components/TeachingPopover/useTeachingPopoverContextValues.ts @@ -0,0 +1,7 @@ +'use client'; + +import { usePopoverContextValues_unstable } from '@fluentui/react-popover'; +import type { TeachingPopoverContextValues, TeachingPopoverState } from './TeachingPopover.types'; + +export const useTeachingPopoverContextValues_unstable: (state: TeachingPopoverState) => TeachingPopoverContextValues = + usePopoverContextValues_unstable; diff --git a/packages/react-components/react-teaching-popover/library/src/index.ts b/packages/react-components/react-teaching-popover/library/src/index.ts index c4aaed60540b8..26a0fc99217ee 100644 --- a/packages/react-components/react-teaching-popover/library/src/index.ts +++ b/packages/react-components/react-teaching-popover/library/src/index.ts @@ -117,7 +117,12 @@ export type { TeachingPopoverTriggerProps, TeachingPopoverTriggerState, } from './TeachingPopoverTrigger'; -export { TeachingPopover, renderTeachingPopover_unstable, useTeachingPopover_unstable } from './TeachingPopover'; +export { + TeachingPopover, + renderTeachingPopover_unstable, + useTeachingPopover_unstable, + useTeachingPopoverContextValues_unstable, +} from './TeachingPopover'; export type { TeachingPopoverProps, TeachingPopoverState } from './TeachingPopover'; export { TeachingPopoverFooter,