diff --git a/packages/react/src/accordion/item/AccordionItem.tsx b/packages/react/src/accordion/item/AccordionItem.tsx index 5b9815b63be..a0c0aa42d31 100644 --- a/packages/react/src/accordion/item/AccordionItem.tsx +++ b/packages/react/src/accordion/item/AccordionItem.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'; +import { useState } from '@base-ui-components/utils/useState'; import { BaseUIComponentProps } from '../../utils/types'; import { useBaseUiId } from '../../utils/useBaseUiId'; import { useCollapsibleRoot } from '../../collapsible/root/useCollapsibleRoot'; @@ -112,7 +113,7 @@ export const AccordionItem = React.forwardRef(function AccordionItem( [disabled, index, isOpen, rootState], ); - const [triggerId, setTriggerId] = React.useState(useBaseUiId()); + const [triggerId, setTriggerId] = useState(useBaseUiId()); const accordionItemContext: AccordionItemContext = React.useMemo( () => ({ diff --git a/packages/react/src/accordion/root/AccordionRoot.tsx b/packages/react/src/accordion/root/AccordionRoot.tsx index 53727d91ee9..35d51d0ac38 100644 --- a/packages/react/src/accordion/root/AccordionRoot.tsx +++ b/packages/react/src/accordion/root/AccordionRoot.tsx @@ -4,6 +4,7 @@ import { useControlled } from '@base-ui-components/utils/useControlled'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { warn } from '@base-ui-components/utils/warn'; +import { useRef } from '@base-ui-components/utils/useRef'; import { BaseUIComponentProps, Orientation } from '../../utils/types'; import { CompositeList } from '../../composite/list/CompositeList'; import { useDirection } from '../../direction-provider/DirectionContext'; @@ -69,7 +70,7 @@ export const AccordionRoot = React.forwardRef(function AccordionRoot( const onValueChange = useStableCallback(onValueChangeProp); - const accordionItemRefs = React.useRef<(HTMLElement | null)[]>([]); + const accordionItemRefs = useRef<(HTMLElement | null)[]>([]); const [value, setValue] = useControlled({ controlled: valueProp, diff --git a/packages/react/src/autocomplete/root/AutocompleteRoot.spec.tsx b/packages/react/src/autocomplete/root/AutocompleteRoot.spec.tsx index f8196a2c9d5..8165ba55f29 100644 --- a/packages/react/src/autocomplete/root/AutocompleteRoot.spec.tsx +++ b/packages/react/src/autocomplete/root/AutocompleteRoot.spec.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useState } from '@base-ui-components/utils/useState'; import { Autocomplete } from '@base-ui-components/react/autocomplete'; const objectItems = [ @@ -105,7 +106,7 @@ const groupItemsReadonly = [ />; function App2() { - const [value, setValue] = React.useState('a'); + const [value, setValue] = useState('a'); return ( ( // Mirror the typed value for uncontrolled usage so we can compose the temporary // inline input value. const isControlled = value !== undefined; - const [internalValue, setInternalValue] = React.useState(defaultValue ?? ''); - const [inlineInputValue, setInlineInputValue] = React.useState(''); + const [internalValue, setInternalValue] = useState(defaultValue ?? ''); + const [inlineInputValue, setInlineInputValue] = useState(''); - React.useEffect(() => { + useEffect(() => { if (isControlled) { setInlineInputValue(''); } diff --git a/packages/react/src/avatar/fallback/AvatarFallback.tsx b/packages/react/src/avatar/fallback/AvatarFallback.tsx index 451fa79f51a..90b77c63eec 100644 --- a/packages/react/src/avatar/fallback/AvatarFallback.tsx +++ b/packages/react/src/avatar/fallback/AvatarFallback.tsx @@ -1,6 +1,8 @@ 'use client'; import * as React from 'react'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useState } from '@base-ui-components/utils/useState'; import { BaseUIComponentProps } from '../../utils/types'; import { useRenderElement } from '../../utils/useRenderElement'; import { useAvatarRootContext } from '../root/AvatarRootContext'; @@ -20,10 +22,10 @@ export const AvatarFallback = React.forwardRef(function AvatarFallback( const { className, render, delay, ...elementProps } = componentProps; const { imageLoadingStatus } = useAvatarRootContext(); - const [delayPassed, setDelayPassed] = React.useState(delay === undefined); + const [delayPassed, setDelayPassed] = useState(delay === undefined); const timeout = useTimeout(); - React.useEffect(() => { + useEffect(() => { if (delay !== undefined) { timeout.start(delay, () => setDelayPassed(true)); } diff --git a/packages/react/src/avatar/image/useImageLoadingStatus.ts b/packages/react/src/avatar/image/useImageLoadingStatus.ts index 9828a914f94..1da7c152857 100644 --- a/packages/react/src/avatar/image/useImageLoadingStatus.ts +++ b/packages/react/src/avatar/image/useImageLoadingStatus.ts @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useState } from '@base-ui-components/utils/useState'; import { NOOP } from '../../utils/noop'; export type ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error'; @@ -14,7 +15,7 @@ export function useImageLoadingStatus( src: string | undefined, { referrerPolicy, crossOrigin }: UseImageLoadingStatusOptions, ): ImageLoadingStatus { - const [loadingStatus, setLoadingStatus] = React.useState('idle'); + const [loadingStatus, setLoadingStatus] = useState('idle'); useIsoLayoutEffect(() => { if (!src) { diff --git a/packages/react/src/avatar/root/AvatarRoot.tsx b/packages/react/src/avatar/root/AvatarRoot.tsx index e43da45c4aa..e2b91e4ffa9 100644 --- a/packages/react/src/avatar/root/AvatarRoot.tsx +++ b/packages/react/src/avatar/root/AvatarRoot.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useState } from '@base-ui-components/utils/useState'; import { BaseUIComponentProps } from '../../utils/types'; import { useRenderElement } from '../../utils/useRenderElement'; import { AvatarRootContext } from './AvatarRootContext'; @@ -17,7 +18,7 @@ export const AvatarRoot = React.forwardRef(function AvatarRoot( ) { const { className, render, ...elementProps } = componentProps; - const [imageLoadingStatus, setImageLoadingStatus] = React.useState('idle'); + const [imageLoadingStatus, setImageLoadingStatus] = useState('idle'); const state: AvatarRoot.State = React.useMemo( () => ({ diff --git a/packages/react/src/checkbox-group/CheckboxGroup.tsx b/packages/react/src/checkbox-group/CheckboxGroup.tsx index 4fc3486444b..5e653524cfa 100644 --- a/packages/react/src/checkbox-group/CheckboxGroup.tsx +++ b/packages/react/src/checkbox-group/CheckboxGroup.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import { useControlled } from '@base-ui-components/utils/useControlled'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { useBaseUiId } from '../utils/useBaseUiId'; import { useRenderElement } from '../utils/useRenderElement'; import { CheckboxGroupContext } from './CheckboxGroupContext'; @@ -83,9 +85,9 @@ export const CheckboxGroup = React.forwardRef(function CheckboxGroup( const id = useBaseUiId(idProp); - const controlRef = React.useRef(null); + const controlRef = useRef(null); - const registerControlRef = React.useCallback((element: HTMLButtonElement | null) => { + const registerControlRef = useCallback((element: HTMLButtonElement | null) => { if (controlRef.current == null && element != null && !element.hasAttribute(PARENT_CHECKBOX)) { controlRef.current = element; } diff --git a/packages/react/src/checkbox-group/useCheckboxGroupParent.ts b/packages/react/src/checkbox-group/useCheckboxGroupParent.ts index 7accbdee3f0..0ac737d83a0 100644 --- a/packages/react/src/checkbox-group/useCheckboxGroupParent.ts +++ b/packages/react/src/checkbox-group/useCheckboxGroupParent.ts @@ -1,6 +1,9 @@ 'use client'; import * as React from 'react'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; +import { useState } from '@base-ui-components/utils/useState'; import { useBaseUiId } from '../utils/useBaseUiId'; import type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails'; import type { BaseUIEventReasons } from '../utils/reasons'; @@ -12,10 +15,10 @@ export function useCheckboxGroupParent( ): useCheckboxGroupParent.ReturnValue { const { allValues = EMPTY, value = EMPTY, onValueChange: onValueChangeProp } = params; - const uncontrolledStateRef = React.useRef(value); - const disabledStatesRef = React.useRef(new Map()); + const uncontrolledStateRef = useRef(value); + const disabledStatesRef = useRef(new Map()); - const [status, setStatus] = React.useState<'on' | 'off' | 'mixed'>('mixed'); + const [status, setStatus] = useState<'on' | 'off' | 'mixed'>('mixed'); const id = useBaseUiId(); const checked = value.length === allValues.length; @@ -23,7 +26,7 @@ export function useCheckboxGroupParent( const onValueChange = useStableCallback(onValueChangeProp); - const getParentProps: useCheckboxGroupParent.ReturnValue['getParentProps'] = React.useCallback( + const getParentProps: useCheckboxGroupParent.ReturnValue['getParentProps'] = useCallback( () => ({ id, indeterminate, @@ -74,7 +77,7 @@ export function useCheckboxGroupParent( [allValues, checked, id, indeterminate, onValueChange, status, value.length], ); - const getChildProps: useCheckboxGroupParent.ReturnValue['getChildProps'] = React.useCallback( + const getChildProps: useCheckboxGroupParent.ReturnValue['getChildProps'] = useCallback( (childValue: string) => ({ checked: value.includes(childValue), onCheckedChange(nextChecked, eventDetails) { diff --git a/packages/react/src/checkbox/indicator/CheckboxIndicator.tsx b/packages/react/src/checkbox/indicator/CheckboxIndicator.tsx index 22b56ac7997..539a30e1b6a 100644 --- a/packages/react/src/checkbox/indicator/CheckboxIndicator.tsx +++ b/packages/react/src/checkbox/indicator/CheckboxIndicator.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useRef } from '@base-ui-components/utils/useRef'; import { useCheckboxRootContext } from '../root/CheckboxRootContext'; import { useRenderElement } from '../../utils/useRenderElement'; import { useStateAttributesMapping } from '../utils/useStateAttributesMapping'; @@ -29,7 +30,7 @@ export const CheckboxIndicator = React.forwardRef(function CheckboxIndicator( const { transitionStatus, setMounted } = useTransitionStatus(rendered); - const indicatorRef = React.useRef(null); + const indicatorRef = useRef(null); const state: CheckboxIndicator.State = React.useMemo( () => ({ diff --git a/packages/react/src/checkbox/root/CheckboxRoot.tsx b/packages/react/src/checkbox/root/CheckboxRoot.tsx index 0fbd7667f26..9201e2adde5 100644 --- a/packages/react/src/checkbox/root/CheckboxRoot.tsx +++ b/packages/react/src/checkbox/root/CheckboxRoot.tsx @@ -6,6 +6,8 @@ import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'; import { visuallyHidden } from '@base-ui-components/utils/visuallyHidden'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { NOOP } from '../../utils/noop'; import { useStateAttributesMapping } from '../utils/useStateAttributesMapping'; import { useRenderElement } from '../../utils/useRenderElement'; @@ -117,7 +119,7 @@ export const CheckboxRoot = React.forwardRef(function CheckboxRoot( const setGroupValue = groupContext?.setValue; const defaultGroupValue = groupContext?.defaultValue; - const controlRef = React.useRef(null); + const controlRef = useRef(null); const { getButtonProps, buttonRef } = useButton({ disabled, @@ -157,7 +159,7 @@ export const CheckboxRoot = React.forwardRef(function CheckboxRoot( getValue: () => checked, }); - const inputRef = React.useRef(null); + const inputRef = useRef(null); const mergedInputRef = useMergedRefs(inputRefProp, inputRef, validation.inputRef); useIsoLayoutEffect(() => { @@ -243,7 +245,7 @@ export const CheckboxRoot = React.forwardRef(function CheckboxRoot( ? groupIndeterminate || indeterminate : indeterminate; - React.useEffect(() => { + useEffect(() => { if (parentContext && value) { parentContext.disabledStatesRef.current.set(value, disabled); } diff --git a/packages/react/src/collapsible/panel/useCollapsiblePanel.ts b/packages/react/src/collapsible/panel/useCollapsiblePanel.ts index 729221be3dc..9891c9a5884 100644 --- a/packages/react/src/collapsible/panel/useCollapsiblePanel.ts +++ b/packages/react/src/collapsible/panel/useCollapsiblePanel.ts @@ -6,6 +6,8 @@ import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'; import { useOnMount } from '@base-ui-components/utils/useOnMount'; import { AnimationFrame, useAnimationFrame } from '@base-ui-components/utils/useAnimationFrame'; import { warn } from '@base-ui-components/utils/warn'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { HTMLProps } from '../../utils/types'; import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails'; import { REASONS } from '../../utils/reasons'; @@ -39,10 +41,10 @@ export function useCollapsiblePanel( width, } = parameters; - const isBeforeMatchRef = React.useRef(false); - const latestAnimationNameRef = React.useRef(null); - const shouldCancelInitialOpenAnimationRef = React.useRef(open); - const shouldCancelInitialOpenTransitionRef = React.useRef(open); + const isBeforeMatchRef = useRef(false); + const latestAnimationNameRef = useRef(null); + const shouldCancelInitialOpenAnimationRef = useRef(open); + const shouldCancelInitialOpenTransitionRef = useRef(open); const endingStyleFrame = useAnimationFrame(); @@ -380,7 +382,7 @@ export function useCollapsiblePanel( } }, [hiddenUntilFound, hidden, animationTypeRef, panelRef]); - React.useEffect( + useEffect( function registerBeforeMatchListener() { const panel = panelRef.current; if (!panel) { diff --git a/packages/react/src/collapsible/root/useCollapsibleRoot.ts b/packages/react/src/collapsible/root/useCollapsibleRoot.ts index ef3ed919c80..3ec1150fb67 100644 --- a/packages/react/src/collapsible/root/useCollapsibleRoot.ts +++ b/packages/react/src/collapsible/root/useCollapsibleRoot.ts @@ -3,6 +3,8 @@ import * as React from 'react'; import { useControlled } from '@base-ui-components/utils/useControlled'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { useBaseUiId } from '../../utils/useBaseUiId'; import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails'; import { REASONS } from '../../utils/reasons'; @@ -32,23 +34,23 @@ export function useCollapsibleRoot( }); const { mounted, setMounted, transitionStatus } = useTransitionStatus(open, true, true); - const [visible, setVisible] = React.useState(open); - const [{ height, width }, setDimensions] = React.useState({ + const [visible, setVisible] = useState(open); + const [{ height, width }, setDimensions] = useState({ height: undefined, width: undefined, }); const defaultPanelId = useBaseUiId(); - const [panelIdState, setPanelIdState] = React.useState(); + const [panelIdState, setPanelIdState] = useState(); const panelId = panelIdState ?? defaultPanelId; - const [hiddenUntilFound, setHiddenUntilFound] = React.useState(false); - const [keepMounted, setKeepMounted] = React.useState(false); + const [hiddenUntilFound, setHiddenUntilFound] = useState(false); + const [keepMounted, setKeepMounted] = useState(false); - const abortControllerRef = React.useRef(null); - const animationTypeRef = React.useRef(null); - const transitionDimensionRef = React.useRef<'width' | 'height' | null>(null); - const panelRef: React.RefObject = React.useRef(null); + const abortControllerRef = useRef(null); + const animationTypeRef = useRef(null); + const transitionDimensionRef = useRef<'width' | 'height' | null>(null); + const panelRef: React.RefObject = useRef(null); const runOnceAnimationsFinish = useAnimationsFinished(panelRef, false); diff --git a/packages/react/src/combobox/chips/ComboboxChips.tsx b/packages/react/src/combobox/chips/ComboboxChips.tsx index 739a2462a45..57f7987ff79 100644 --- a/packages/react/src/combobox/chips/ComboboxChips.tsx +++ b/packages/react/src/combobox/chips/ComboboxChips.tsx @@ -1,6 +1,8 @@ 'use client'; import * as React from 'react'; import { useStore } from '@base-ui-components/utils/store'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { useRenderElement } from '../../utils/useRenderElement'; import { BaseUIComponentProps } from '../../utils/types'; import { ComboboxChipsContext } from './ComboboxChipsContext'; @@ -22,7 +24,7 @@ export const ComboboxChips = React.forwardRef(function ComboboxChips( const open = useStore(store, selectors.open); - const [highlightedChipIndex, setHighlightedChipIndex] = React.useState( + const [highlightedChipIndex, setHighlightedChipIndex] = useState( undefined, ); @@ -30,7 +32,7 @@ export const ComboboxChips = React.forwardRef(function ComboboxChips( setHighlightedChipIndex(undefined); } - const chipsRef = React.useRef>([]); + const chipsRef = useRef>([]); const element = useRenderElement('div', componentProps, { ref: [forwardedRef, store.state.chipsContainerRef], diff --git a/packages/react/src/combobox/group/ComboboxGroup.tsx b/packages/react/src/combobox/group/ComboboxGroup.tsx index 908d059aec7..9c03b392513 100644 --- a/packages/react/src/combobox/group/ComboboxGroup.tsx +++ b/packages/react/src/combobox/group/ComboboxGroup.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useState } from '@base-ui-components/utils/useState'; import { BaseUIComponentProps } from '../../utils/types'; import { useRenderElement } from '../../utils/useRenderElement'; import { ComboboxGroupContext } from './ComboboxGroupContext'; @@ -15,7 +16,7 @@ export const ComboboxGroup = React.forwardRef(function ComboboxGroup( ) { const { render, className, items, ...elementProps } = componentProps; - const [labelId, setLabelId] = React.useState(); + const [labelId, setLabelId] = useState(); const contextValue = React.useMemo( () => ({ diff --git a/packages/react/src/combobox/input/ComboboxInput.tsx b/packages/react/src/combobox/input/ComboboxInput.tsx index 76c1e7619d0..ef363a5d9a2 100644 --- a/packages/react/src/combobox/input/ComboboxInput.tsx +++ b/packages/react/src/combobox/input/ComboboxInput.tsx @@ -3,6 +3,8 @@ import * as React from 'react'; import { useStore } from '@base-ui-components/utils/store'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { isAndroid, isFirefox } from '@base-ui-components/utils/detectBrowser'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { BaseUIComponentProps } from '../../utils/types'; import { useBaseUiId } from '../../utils/useBaseUiId'; import { useRenderElement } from '../../utils/useRenderElement'; @@ -88,8 +90,8 @@ export const ComboboxInput = React.forwardRef(function ComboboxInput( const disabled = fieldDisabled || comboboxDisabled || disabledProp; const listEmpty = filteredItems.length === 0; - const [composingValue, setComposingValue] = React.useState(null); - const isComposingRef = React.useRef(false); + const [composingValue, setComposingValue] = useState(null); + const isComposingRef = useRef(false); const setInputElement = useStableCallback((element: HTMLInputElement | null) => { const isInsidePopup = hasPositionerParent || store.state.inline; diff --git a/packages/react/src/combobox/item-indicator/ComboboxItemIndicator.tsx b/packages/react/src/combobox/item-indicator/ComboboxItemIndicator.tsx index 43f41584723..51d8855e7f9 100644 --- a/packages/react/src/combobox/item-indicator/ComboboxItemIndicator.tsx +++ b/packages/react/src/combobox/item-indicator/ComboboxItemIndicator.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useRef } from '@base-ui-components/utils/useRef'; import type { BaseUIComponentProps } from '../../utils/types'; import { useComboboxItemContext } from '../item/ComboboxItemContext'; import { type TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus'; @@ -40,7 +41,7 @@ const Inner = React.memo( const { selected } = useComboboxItemContext(); - const indicatorRef = React.useRef(null); + const indicatorRef = useRef(null); const { transitionStatus, setMounted } = useTransitionStatus(selected); diff --git a/packages/react/src/combobox/item/ComboboxItem.tsx b/packages/react/src/combobox/item/ComboboxItem.tsx index 8affecbf08f..ba8f4fc5e32 100644 --- a/packages/react/src/combobox/item/ComboboxItem.tsx +++ b/packages/react/src/combobox/item/ComboboxItem.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { useStore } from '@base-ui-components/utils/store'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { useComboboxRootContext, useComboboxDerivedItemsContext, @@ -38,8 +39,8 @@ export const ComboboxItem = React.memo( ...elementProps } = componentProps; - const didPointerDownRef = React.useRef(false); - const textRef = React.useRef(null); + const didPointerDownRef = useRef(false); + const textRef = useRef(null); const listItem = useCompositeListItem({ index: indexProp, textRef, @@ -68,7 +69,7 @@ export const ComboboxItem = React.memo( const items = useStore(store, selectors.items); const getItemProps = useStore(store, selectors.getItemProps); - const itemRef = React.useRef(null); + const itemRef = useRef(null); const id = rootId != null && hasRegistered ? `${rootId}-${index}` : undefined; const selected = matchesSelectedValue && selectable; diff --git a/packages/react/src/combobox/root/AriaCombobox.tsx b/packages/react/src/combobox/root/AriaCombobox.tsx index ad6dec3d9cd..1a91e238b23 100644 --- a/packages/react/src/combobox/root/AriaCombobox.tsx +++ b/packages/react/src/combobox/root/AriaCombobox.tsx @@ -9,6 +9,9 @@ import { visuallyHidden } from '@base-ui-components/utils/visuallyHidden'; import { useRefWithInit } from '@base-ui-components/utils/useRefWithInit'; import { Store, useStore } from '@base-ui-components/utils/store'; import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { ElementProps, useDismiss, @@ -125,33 +128,33 @@ export function AriaCombobox( const id = useLabelableId({ id: idProp }); const collatorFilter = useCoreFilter({ locale }); - const [queryChangedAfterOpen, setQueryChangedAfterOpen] = React.useState(false); - const [closeQuery, setCloseQuery] = React.useState(null); - - const listRef = React.useRef>([]); - const labelsRef = React.useRef>([]); - const popupRef = React.useRef(null); - const inputRef = React.useRef(null); - const emptyRef = React.useRef(null); - const keyboardActiveRef = React.useRef(true); - const hadInputClearRef = React.useRef(false); - const chipsContainerRef = React.useRef(null); - const clearRef = React.useRef(null); - const selectionEventRef = React.useRef(null); - const lastHighlightRef = React.useRef(INITIAL_LAST_HIGHLIGHT); - const pendingQueryHighlightRef = React.useRef(null); + const [queryChangedAfterOpen, setQueryChangedAfterOpen] = useState(false); + const [closeQuery, setCloseQuery] = useState(null); + + const listRef = useRef>([]); + const labelsRef = useRef>([]); + const popupRef = useRef(null); + const inputRef = useRef(null); + const emptyRef = useRef(null); + const keyboardActiveRef = useRef(true); + const hadInputClearRef = useRef(false); + const chipsContainerRef = useRef(null); + const clearRef = useRef(null); + const selectionEventRef = useRef(null); + const lastHighlightRef = useRef(INITIAL_LAST_HIGHLIGHT); + const pendingQueryHighlightRef = useRef(null); /** * Contains the currently visible list of item values post-filtering. */ - const valuesRef = React.useRef([]); + const valuesRef = useRef([]); /** * Contains all item values in a stable, unfiltered order. * This is only used when `items` prop is not provided. * It accumulates values on first mount and does not remove them on unmount due to * filtering, providing a stable index for selected value tracking. */ - const allValuesRef = React.useRef([]); + const allValuesRef = useRef([]); const disabled = fieldDisabled || disabledProp; const name = fieldName ?? nameProp; @@ -436,7 +439,7 @@ export function AriaCombobox( } }); - const initialSelectedValueRef = React.useRef(selectedValue); + const initialSelectedValueRef = useRef(selectedValue); useIsoLayoutEffect(() => { // Ensure the values and labels are registered for programmatic value changes. if (selectedValue !== initialSelectedValueRef.current) { @@ -896,7 +899,7 @@ export function AriaCombobox( // Ensures that the active index is not set to 0 when the list is empty. // This avoids needing to press ArrowDown twice under certain conditions. - React.useEffect(() => { + useEffect(() => { if (hasItems && autoHighlightMode && flatFilteredItems.length === 0) { setIndices({ activeIndex: null }); } diff --git a/packages/react/src/combobox/root/ComboboxRoot.spec.tsx b/packages/react/src/combobox/root/ComboboxRoot.spec.tsx index a1d8a10d94a..9b017499ab1 100644 --- a/packages/react/src/combobox/root/ComboboxRoot.spec.tsx +++ b/packages/react/src/combobox/root/ComboboxRoot.spec.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useState } from '@base-ui-components/utils/useState'; import { Combobox } from '@base-ui-components/react/combobox'; import { mergeProps } from '../../merge-props'; @@ -184,7 +185,7 @@ const groupItemsReadonly = [ />; function App() { - const [multiple, setMultiple] = React.useState(false); + const [multiple, setMultiple] = useState(false); return ( ; function App2() { - const [value, setValue] = React.useState('a'); + const [value, setValue] = useState('a'); return ( ('a'); + const [value, setValue] = useState('a'); return ( string) => { if (multiple) { return createCollatorItemFilter(coreFilter, itemToString)(item, query); diff --git a/packages/react/src/combobox/trigger/ComboboxTrigger.tsx b/packages/react/src/combobox/trigger/ComboboxTrigger.tsx index c98233b18ce..d2aca4a65a8 100644 --- a/packages/react/src/combobox/trigger/ComboboxTrigger.tsx +++ b/packages/react/src/combobox/trigger/ComboboxTrigger.tsx @@ -4,6 +4,8 @@ import { useStore } from '@base-ui-components/utils/store'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; import { ownerDocument } from '@base-ui-components/utils/owner'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { BaseUIComponentProps, NativeButtonProps } from '../../utils/types'; import { useRenderElement } from '../../utils/useRenderElement'; import { useButton } from '../../use-button'; @@ -78,7 +80,7 @@ export const ComboboxTrigger = React.forwardRef(function ComboboxTrigger( const disabled = fieldDisabled || comboboxDisabled || disabledProp; - const currentPointerTypeRef = React.useRef(''); + const currentPointerTypeRef = useRef(''); function trackPointerType(event: React.PointerEvent) { currentPointerTypeRef.current = event.pointerType; @@ -88,7 +90,7 @@ export const ComboboxTrigger = React.forwardRef(function ComboboxTrigger( // Update the floating root context to use the trigger element when it differs from the current reference. // This ensures useClick and useTypeahead attach handlers to the correct element. - React.useEffect(() => { + useEffect(() => { if (!inputInsidePopup) { return; } diff --git a/packages/react/src/composite/item/useCompositeItem.ts b/packages/react/src/composite/item/useCompositeItem.ts index 1159564f025..ca1e043542f 100644 --- a/packages/react/src/composite/item/useCompositeItem.ts +++ b/packages/react/src/composite/item/useCompositeItem.ts @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'; +import { useRef } from '@base-ui-components/utils/useRef'; import { useCompositeRootContext } from '../root/CompositeRootContext'; import { useCompositeListItem } from '../list/useCompositeListItem'; import { HTMLProps } from '../../utils/types'; @@ -16,7 +17,7 @@ export function useCompositeItem(params: UseCompositeItemParameters(null); + const itemRef = useRef(null); const mergedRef = useMergedRefs(ref, itemRef); const compositeProps = React.useMemo( diff --git a/packages/react/src/composite/list/CompositeList.tsx b/packages/react/src/composite/list/CompositeList.tsx index 35bb2326cba..6691a1d47ad 100644 --- a/packages/react/src/composite/list/CompositeList.tsx +++ b/packages/react/src/composite/list/CompositeList.tsx @@ -4,6 +4,8 @@ import * as React from 'react'; import { useRefWithInit } from '@base-ui-components/utils/useRefWithInit'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { CompositeListContext } from './CompositeListContext'; export type CompositeMetadata = { index?: number | null } & CustomMetadata; @@ -17,7 +19,7 @@ export function CompositeList(props: CompositeList.Props) { const onMapChange = useStableCallback(onMapChangeProp); - const nextIndexRef = React.useRef(0); + const nextIndexRef = useRef(0); const listeners = useRefWithInit(createListeners).current; // We use a stable `map` to avoid O(n^2) re-allocation costs for large lists. @@ -30,8 +32,8 @@ export function CompositeList(props: CompositeList.Props) { const map = useRefWithInit(createMap).current; // `mapTick` uses a counter rather than objects for low precision-loss risk and better memory efficiency - const [mapTick, setMapTick] = React.useState(0); - const lastTickRef = React.useRef(mapTick); + const [mapTick, setMapTick] = useState(0); + const lastTickRef = useRef(mapTick); const register = useStableCallback((node: Element, metadata: Metadata) => { map.set(node, metadata ?? null); diff --git a/packages/react/src/composite/list/useCompositeListItem.ts b/packages/react/src/composite/list/useCompositeListItem.ts index e94eb937586..a10926cce65 100644 --- a/packages/react/src/composite/list/useCompositeListItem.ts +++ b/packages/react/src/composite/list/useCompositeListItem.ts @@ -1,6 +1,9 @@ 'use client'; import * as React from 'react'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; +import { useState } from '@base-ui-components/utils/useState'; import { useCompositeListContext } from './CompositeListContext'; export interface UseCompositeListItemParameters { @@ -35,8 +38,8 @@ export function useCompositeListItem( const { register, unregister, subscribeMapChange, elementsRef, labelsRef, nextIndexRef } = useCompositeListContext(); - const indexRef = React.useRef(-1); - const [index, setIndex] = React.useState( + const indexRef = useRef(-1); + const [index, setIndex] = useState( externalIndex ?? (indexGuessBehavior === IndexGuessBehavior.GuessFromOrder ? () => { @@ -50,9 +53,9 @@ export function useCompositeListItem( : -1), ); - const componentRef = React.useRef(null); + const componentRef = useRef(null); - const ref = React.useCallback( + const ref = useCallback( (node: HTMLElement | null) => { componentRef.current = node; diff --git a/packages/react/src/composite/root/useCompositeRoot.ts b/packages/react/src/composite/root/useCompositeRoot.ts index d691af09381..46dccb77a3a 100644 --- a/packages/react/src/composite/root/useCompositeRoot.ts +++ b/packages/react/src/composite/root/useCompositeRoot.ts @@ -3,6 +3,8 @@ import * as React from 'react'; import { isElementDisabled } from '@base-ui-components/utils/isElementDisabled'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import type { TextDirection } from '../../direction-provider/DirectionContext'; import { ALL_KEYS, @@ -90,15 +92,15 @@ export function useCompositeRoot(params: UseCompositeRootParameters) { modifierKeys = EMPTY_ARRAY, } = params; - const [internalHighlightedIndex, internalSetHighlightedIndex] = React.useState(0); + const [internalHighlightedIndex, internalSetHighlightedIndex] = useState(0); const isGrid = cols > 1; - const rootRef = React.useRef(null); + const rootRef = useRef(null); const mergedRef = useMergedRefs(rootRef, externalRef); - const elementsRef = React.useRef>([]); - const hasSetDefaultIndexRef = React.useRef(false); + const elementsRef = useRef>([]); + const hasSetDefaultIndexRef = useRef(false); const highlightedIndex = externalHighlightedIndex ?? internalHighlightedIndex; const onHighlightedIndexChange = useStableCallback((index, shouldScrollIntoView = false) => { diff --git a/packages/react/src/context-menu/root/ContextMenuRoot.tsx b/packages/react/src/context-menu/root/ContextMenuRoot.tsx index 8d32cc1fa74..d1c6c585849 100644 --- a/packages/react/src/context-menu/root/ContextMenuRoot.tsx +++ b/packages/react/src/context-menu/root/ContextMenuRoot.tsx @@ -1,6 +1,8 @@ 'use client'; import * as React from 'react'; import { useId } from '@base-ui-components/utils/useId'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { ContextMenuRootContext } from './ContextMenuRootContext'; import { Menu } from '../../menu'; import { MenuRootContext } from '../../menu/root/MenuRootContext'; @@ -14,18 +16,18 @@ import type { MenuRoot } from '../../menu/root/MenuRoot'; * Documentation: [Base UI Context Menu](https://base-ui.com/react/components/context-menu) */ export function ContextMenuRoot(props: ContextMenuRoot.Props) { - const [anchor, setAnchor] = React.useState({ + const [anchor, setAnchor] = useState({ getBoundingClientRect() { return DOMRect.fromRect({ width: 0, height: 0, x: 0, y: 0 }); }, }); - const backdropRef = React.useRef(null); - const internalBackdropRef = React.useRef(null); - const actionsRef: ContextMenuRootContext['actionsRef'] = React.useRef(null); - const positionerRef = React.useRef(null); - const allowMouseUpTriggerRef = React.useRef(true); - const initialCursorPointRef = React.useRef<{ x: number; y: number } | null>(null); + const backdropRef = useRef(null); + const internalBackdropRef = useRef(null); + const actionsRef: ContextMenuRootContext['actionsRef'] = useRef(null); + const positionerRef = useRef(null); + const allowMouseUpTriggerRef = useRef(true); + const initialCursorPointRef = useRef<{ x: number; y: number } | null>(null); const id = useId(); const contextValue: ContextMenuRootContext = React.useMemo( diff --git a/packages/react/src/context-menu/trigger/ContextMenuTrigger.tsx b/packages/react/src/context-menu/trigger/ContextMenuTrigger.tsx index 5285b277a24..e446ad973b0 100644 --- a/packages/react/src/context-menu/trigger/ContextMenuTrigger.tsx +++ b/packages/react/src/context-menu/trigger/ContextMenuTrigger.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import { ownerDocument } from '@base-ui-components/utils/owner'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { contains, getTarget, stopEvent } from '../../floating-ui-react/utils'; import type { BaseUIComponentProps } from '../../utils/types'; import { useContextMenuRootContext } from '../root/ContextMenuRootContext'; @@ -40,11 +42,11 @@ export const ContextMenuTrigger = React.forwardRef(function ContextMenuTrigger( const { store } = useMenuRootContext(false); const open = store.useState('open'); - const triggerRef = React.useRef(null); - const touchPositionRef = React.useRef<{ x: number; y: number } | null>(null); + const triggerRef = useRef(null); + const touchPositionRef = useRef<{ x: number; y: number } | null>(null); const longPressTimeout = useTimeout(); const allowMouseUpTimeout = useTimeout(); - const allowMouseUpRef = React.useRef(false); + const allowMouseUpRef = useRef(false); function handleLongPress(x: number, y: number, event: MouseEvent | TouchEvent) { const isTouchEvent = event.type.startsWith('touch'); @@ -144,7 +146,7 @@ export const ContextMenuTrigger = React.forwardRef(function ContextMenuTrigger( touchPositionRef.current = null; } - React.useEffect(() => { + useEffect(() => { function handleDocumentContextMenu(event: MouseEvent) { const target = getTarget(event); const targetElement = target as HTMLElement | null; diff --git a/packages/react/src/dialog/root/useDialogRoot.ts b/packages/react/src/dialog/root/useDialogRoot.ts index 78bcfa32609..14b65b4d67a 100644 --- a/packages/react/src/dialog/root/useDialogRoot.ts +++ b/packages/react/src/dialog/root/useDialogRoot.ts @@ -2,6 +2,9 @@ import * as React from 'react'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useScrollLock } from '@base-ui-components/utils/useScrollLock'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useCallback } from '@base-ui-components/utils/useCallback'; +import { useState } from '@base-ui-components/utils/useState'; import { useDismiss, useInteractions, @@ -47,7 +50,7 @@ export function useDialogRoot(params: useDialogRoot.Parameters): useDialogRoot.R return details; }); - const handleImperativeClose = React.useCallback(() => { + const handleImperativeClose = useCallback(() => { store.setOpen(false, createDialogEventDetails(REASONS.imperativeAction)); }, [store, createDialogEventDetails]); @@ -64,7 +67,7 @@ export function useDialogRoot(params: useDialogRoot.Parameters): useDialogRoot.R noEmit: true, }); - const [ownNestedOpenDialogs, setOwnNestedOpenDialogs] = React.useState(0); + const [ownNestedOpenDialogs, setOwnNestedOpenDialogs] = useState(0); const isTopmost = ownNestedOpenDialogs === 0; const role = useRole(floatingRootContext); @@ -124,7 +127,7 @@ export function useDialogRoot(params: useDialogRoot.Parameters): useDialogRoot.R }); // Notify parent of our open/close state using parent callbacks, if any - React.useEffect(() => { + useEffect(() => { if (parentContext?.onNestedDialogOpen && open) { parentContext.onNestedDialogOpen(ownNestedOpenDialogs); } diff --git a/packages/react/src/dialog/trigger/DialogTrigger.tsx b/packages/react/src/dialog/trigger/DialogTrigger.tsx index cb763697bc2..29647f055fc 100644 --- a/packages/react/src/dialog/trigger/DialogTrigger.tsx +++ b/packages/react/src/dialog/trigger/DialogTrigger.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useState } from '@base-ui-components/utils/useState'; import { useDialogRootContext } from '../root/DialogRootContext'; import { useButton } from '../../use-button/useButton'; import { useRenderElement } from '../../utils/useRenderElement'; @@ -44,7 +45,7 @@ export const DialogTrigger = React.forwardRef(function DialogTrigger( const floatingContext = store.useState('floatingRootContext'); const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId); - const [triggerElement, setTriggerElement] = React.useState(null); + const [triggerElement, setTriggerElement] = useState(null); const { registerTrigger, isMountedByThisTrigger } = useTriggerDataForwarding( thisTriggerId, diff --git a/packages/react/src/field/label/FieldLabel.tsx b/packages/react/src/field/label/FieldLabel.tsx index 0b5248a7288..a4fd4bf6faa 100644 --- a/packages/react/src/field/label/FieldLabel.tsx +++ b/packages/react/src/field/label/FieldLabel.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { getTarget } from '../../floating-ui-react/utils'; import { FieldRoot } from '../root/FieldRoot'; import { useFieldRootContext } from '../root/FieldRootContext'; @@ -28,7 +29,7 @@ export const FieldLabel = React.forwardRef(function FieldLabel( const id = useBaseUiId(idProp); - const labelRef = React.useRef(null); + const labelRef = useRef(null); useIsoLayoutEffect(() => { if (id) { diff --git a/packages/react/src/field/root/FieldRoot.tsx b/packages/react/src/field/root/FieldRoot.tsx index 5ec608785ce..6c129a49afb 100644 --- a/packages/react/src/field/root/FieldRoot.tsx +++ b/packages/react/src/field/root/FieldRoot.tsx @@ -1,6 +1,8 @@ 'use client'; import * as React from 'react'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { FieldRootContext } from './FieldRootContext'; import { DEFAULT_VALIDITY_STATE, fieldValidityMapping } from '../utils/constants'; import { useFieldsetRootContext } from '../../fieldset/root/FieldsetRootContext'; @@ -40,15 +42,15 @@ const FieldRootInner = React.forwardRef(function FieldRootInner( const disabled = disabledFieldset || disabledProp; - const [touchedState, setTouchedUnwrapped] = React.useState(false); - const [dirtyState, setDirtyUnwrapped] = React.useState(false); - const [filled, setFilled] = React.useState(false); - const [focused, setFocused] = React.useState(false); + const [touchedState, setTouchedUnwrapped] = useState(false); + const [dirtyState, setDirtyUnwrapped] = useState(false); + const [filled, setFilled] = useState(false); + const [focused, setFocused] = useState(false); const dirty = dirtyProp ?? dirtyState; const touched = touchedProp ?? touchedState; - const markedDirtyRef = React.useRef(false); + const markedDirtyRef = useRef(false); const setDirty: typeof setDirtyUnwrapped = useStableCallback((value) => { if (dirtyProp !== undefined) { @@ -78,7 +80,7 @@ const FieldRootInner = React.forwardRef(function FieldRootInner( invalidProp || (name && {}.hasOwnProperty.call(errors, name) && errors[name] !== undefined), ); - const [validityData, setValidityData] = React.useState({ + const [validityData, setValidityData] = useState({ state: DEFAULT_VALIDITY_STATE, error: '', errors: [], diff --git a/packages/react/src/field/root/useFieldValidation.ts b/packages/react/src/field/root/useFieldValidation.ts index 0976aed8fc4..c9d25c1af5a 100644 --- a/packages/react/src/field/root/useFieldValidation.ts +++ b/packages/react/src/field/root/useFieldValidation.ts @@ -3,6 +3,8 @@ import * as React from 'react'; import { EMPTY_OBJECT } from '@base-ui-components/utils/empty'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { useLabelableContext } from '../../labelable-provider/LabelableContext'; import { mergeProps } from '../../merge-props'; import { DEFAULT_VALIDITY_STATE } from '../utils/constants'; @@ -56,7 +58,7 @@ export function useFieldValidation( const { controlId, getDescriptionProps } = useLabelableContext(); const timeout = useTimeout(); - const inputRef = React.useRef(null); + const inputRef = useRef(null); const commit = useStableCallback(async (value: unknown, revalidate = false) => { const element = inputRef.current; @@ -232,7 +234,7 @@ export function useFieldValidation( setValidityData(nextValidityData); }); - const getValidationProps = React.useCallback( + const getValidationProps = useCallback( (externalProps = {}) => mergeProps( getDescriptionProps, @@ -242,7 +244,7 @@ export function useFieldValidation( [getDescriptionProps, state.valid], ); - const getInputValidationProps = React.useCallback( + const getInputValidationProps = useCallback( (externalProps = {}) => mergeProps<'input'>( { diff --git a/packages/react/src/fieldset/root/FieldsetRoot.tsx b/packages/react/src/fieldset/root/FieldsetRoot.tsx index b7f68fcb40a..86c7a9cd413 100644 --- a/packages/react/src/fieldset/root/FieldsetRoot.tsx +++ b/packages/react/src/fieldset/root/FieldsetRoot.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useState } from '@base-ui-components/utils/useState'; import { FieldsetRootContext } from './FieldsetRootContext'; import type { BaseUIComponentProps } from '../../utils/types'; import { useRenderElement } from '../../utils/useRenderElement'; @@ -16,7 +17,7 @@ export const FieldsetRoot = React.forwardRef(function FieldsetRoot( ) { const { render, className, disabled = false, ...elementProps } = componentProps; - const [legendId, setLegendId] = React.useState(undefined); + const [legendId, setLegendId] = useState(undefined); const state: FieldsetRoot.State = React.useMemo( () => ({ diff --git a/packages/react/src/floating-ui-react/components/FloatingDelayGroup.tsx b/packages/react/src/floating-ui-react/components/FloatingDelayGroup.tsx index 0d103c0b37e..487ac5ddeb7 100644 --- a/packages/react/src/floating-ui-react/components/FloatingDelayGroup.tsx +++ b/packages/react/src/floating-ui-react/components/FloatingDelayGroup.tsx @@ -1,6 +1,8 @@ import * as React from 'react'; import { useTimeout, Timeout } from '@base-ui-components/utils/useTimeout'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { getDelay } from '../hooks/useHover'; import type { FloatingRootContext, Delay, FloatingContext } from '../types'; @@ -61,10 +63,10 @@ export interface FloatingDelayGroupProps { export function FloatingDelayGroup(props: FloatingDelayGroupProps): React.JSX.Element { const { children, delay, timeoutMs = 0 } = props; - const delayRef = React.useRef(delay); - const initialDelayRef = React.useRef(delay); - const currentIdRef = React.useRef(null); - const currentContextRef = React.useRef(null); + const delayRef = useRef(delay); + const initialDelayRef = useRef(delay); + const currentIdRef = useRef(null); + const currentContextRef = useRef(null); const timeout = useTimeout(); return ( @@ -139,7 +141,7 @@ export function useDelayGroup( timeout, } = groupContext; - const [isInstantPhase, setIsInstantPhase] = React.useState(false); + const [isInstantPhase, setIsInstantPhase] = useState(false); useIsoLayoutEffect(() => { function unset() { diff --git a/packages/react/src/floating-ui-react/components/FloatingFocusManager.tsx b/packages/react/src/floating-ui-react/components/FloatingFocusManager.tsx index dedfacbbfc8..445d9079687 100644 --- a/packages/react/src/floating-ui-react/components/FloatingFocusManager.tsx +++ b/packages/react/src/floating-ui-react/components/FloatingFocusManager.tsx @@ -10,6 +10,8 @@ import { useTimeout } from '@base-ui-components/utils/useTimeout'; import type { InteractionType } from '@base-ui-components/utils/useEnhancedClickHandler'; import { useAnimationFrame } from '@base-ui-components/utils/useAnimationFrame'; import { ownerWindow } from '@base-ui-components/utils/owner'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { FocusGuard } from '../../utils/FocusGuard'; import { activeElement, @@ -299,13 +301,13 @@ export function FloatingFocusManager(props: FloatingFocusManagerProps): React.JS const tree = useFloatingTree(externalTree); const portalContext = usePortalContext(); - const startDismissButtonRef = React.useRef(null); - const endDismissButtonRef = React.useRef(null); - const preventReturnFocusRef = React.useRef(false); - const isPointerDownRef = React.useRef(false); - const tabbableIndexRef = React.useRef(-1); - const closeTypeRef = React.useRef(''); - const lastInteractionTypeRef = React.useRef(''); + const startDismissButtonRef = useRef(null); + const endDismissButtonRef = useRef(null); + const preventReturnFocusRef = useRef(false); + const isPointerDownRef = useRef(false); + const tabbableIndexRef = useRef(-1); + const closeTypeRef = useRef(''); + const lastInteractionTypeRef = useRef(''); const blurTimeout = useTimeout(); const pointerDownTimeout = useTimeout(); @@ -329,7 +331,7 @@ export function FloatingFocusManager(props: FloatingFocusManagerProps): React.JS .flat() as Array; }); - React.useEffect(() => { + useEffect(() => { if (disabled) { return undefined; } @@ -366,7 +368,7 @@ export function FloatingFocusManager(props: FloatingFocusManagerProps): React.JS getTabbableElements, ]); - React.useEffect(() => { + useEffect(() => { if (disabled) { return undefined; } @@ -391,7 +393,7 @@ export function FloatingFocusManager(props: FloatingFocusManagerProps): React.JS }, [disabled, floating, getTabbableContent]); // Track the last interaction type at the document level to disambiguate focus events - React.useEffect(() => { + useEffect(() => { if (disabled || !open) { return undefined; } @@ -415,7 +417,7 @@ export function FloatingFocusManager(props: FloatingFocusManagerProps): React.JS }; }, [disabled, floating, domReference, floatingFocusElement, open]); - React.useEffect(() => { + useEffect(() => { if (disabled) { return undefined; } @@ -597,8 +599,8 @@ export function FloatingFocusManager(props: FloatingFocusManagerProps): React.JS restoreFocusFrame, ]); - const beforeGuardRef = React.useRef(null); - const afterGuardRef = React.useRef(null); + const beforeGuardRef = useRef(null); + const afterGuardRef = useRef(null); const mergedBeforeGuardRef = useMergedRefs( beforeGuardRef, @@ -607,7 +609,7 @@ export function FloatingFocusManager(props: FloatingFocusManagerProps): React.JS ); const mergedAfterGuardRef = useMergedRefs(afterGuardRef, portalContext?.afterInsideRef); - React.useEffect(() => { + useEffect(() => { if (disabled || !floating || !open) { return undefined; } @@ -844,7 +846,7 @@ export function FloatingFocusManager(props: FloatingFocusManagerProps): React.JS getNodeId, ]); - React.useEffect(() => { + useEffect(() => { // The `returnFocus` cleanup behavior is inside a microtask; ensure we // wait for it to complete before resetting the flag. queueMicrotask(() => { @@ -852,7 +854,7 @@ export function FloatingFocusManager(props: FloatingFocusManagerProps): React.JS }); }, [disabled]); - React.useEffect(() => { + useEffect(() => { if (disabled || !open) { return undefined; } diff --git a/packages/react/src/floating-ui-react/components/FloatingPortal.tsx b/packages/react/src/floating-ui-react/components/FloatingPortal.tsx index 0e3e09e5ede..301135ed4e0 100644 --- a/packages/react/src/floating-ui-react/components/FloatingPortal.tsx +++ b/packages/react/src/floating-ui-react/components/FloatingPortal.tsx @@ -3,6 +3,9 @@ import * as ReactDOM from 'react-dom'; import { isNode } from '@floating-ui/utils/dom'; import { useId } from '@base-ui-components/utils/useId'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { FocusGuard } from '../../utils/FocusGuard'; import { enableFocusInside, @@ -67,12 +70,12 @@ export function useFloatingPortalNode( const portalContext = usePortalContext(); const parentPortalNode = portalContext?.portalNode; - const [containerElement, setContainerElement] = React.useState( + const [containerElement, setContainerElement] = useState( null, ); - const [portalNode, setPortalNode] = React.useState(null); + const [portalNode, setPortalNode] = useState(null); - const containerRef = React.useRef(null); + const containerRef = useRef(null); useIsoLayoutEffect(() => { // Wait for the container to be resolved if explicitly `null`. @@ -158,12 +161,12 @@ export const FloatingPortal = React.forwardRef(function FloatingPortal( elementProps, }); - const beforeOutsideRef = React.useRef(null); - const afterOutsideRef = React.useRef(null); - const beforeInsideRef = React.useRef(null); - const afterInsideRef = React.useRef(null); + const beforeOutsideRef = useRef(null); + const afterOutsideRef = useRef(null); + const beforeInsideRef = useRef(null); + const afterInsideRef = useRef(null); - const [focusManagerState, setFocusManagerState] = React.useState(null); + const [focusManagerState, setFocusManagerState] = useState(null); const modal = focusManagerState?.modal; const open = focusManagerState?.open; @@ -174,7 +177,7 @@ export const FloatingPortal = React.forwardRef(function FloatingPortal( : !!focusManagerState && !focusManagerState.modal && focusManagerState.open && !!portalNode; // https://codesandbox.io/s/tabbable-portal-f4tng?file=/src/TabbablePortal.tsx - React.useEffect(() => { + useEffect(() => { if (!portalNode || modal) { return undefined; } @@ -200,7 +203,7 @@ export const FloatingPortal = React.forwardRef(function FloatingPortal( }; }, [portalNode, modal]); - React.useEffect(() => { + useEffect(() => { if (!portalNode || open) { return; } diff --git a/packages/react/src/floating-ui-react/hooks/useClick.ts b/packages/react/src/floating-ui-react/hooks/useClick.ts index 9d42331eeb9..a4ac25d8315 100644 --- a/packages/react/src/floating-ui-react/hooks/useClick.ts +++ b/packages/react/src/floating-ui-react/hooks/useClick.ts @@ -2,6 +2,7 @@ import * as React from 'react'; import { useAnimationFrame } from '@base-ui-components/utils/useAnimationFrame'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; +import { useRef } from '@base-ui-components/utils/useRef'; import { EMPTY_OBJECT } from '../../utils/constants'; import type { ElementProps, FloatingContext, FloatingRootContext } from '../types'; import { isMouseLikePointerType, isTypeableElement } from '../utils'; @@ -66,7 +67,7 @@ export function useClick( touchOpenDelay = 0, } = props; - const pointerTypeRef = React.useRef<'mouse' | 'pen' | 'touch'>(undefined); + const pointerTypeRef = useRef<'mouse' | 'pen' | 'touch'>(undefined); const frame = useAnimationFrame(); const touchOpenTimeout = useTimeout(); diff --git a/packages/react/src/floating-ui-react/hooks/useClientPoint.ts b/packages/react/src/floating-ui-react/hooks/useClientPoint.ts index 155d01657ba..ca19b015d7b 100644 --- a/packages/react/src/floating-ui-react/hooks/useClientPoint.ts +++ b/packages/react/src/floating-ui-react/hooks/useClientPoint.ts @@ -2,6 +2,10 @@ import * as React from 'react'; import { getWindow } from '@floating-ui/utils/dom'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; +import { useState } from '@base-ui-components/utils/useState'; import { contains, getTarget, isMouseLikePointerType } from '../utils'; import type { ContextData, ElementProps, FloatingContext, FloatingRootContext } from '../types'; @@ -128,11 +132,11 @@ export function useClientPoint( const { enabled = true, axis = 'both', x = null, y = null } = props; - const initialRef = React.useRef(false); - const cleanupListenerRef = React.useRef void)>(null); + const initialRef = useRef(false); + const cleanupListenerRef = useRef void)>(null); - const [pointerType, setPointerType] = React.useState(); - const [reactive, setReactive] = React.useState([]); + const [pointerType, setPointerType] = useState(); + const [reactive, setReactive] = useState([]); const setReference = useStableCallback((newX: number | null, newY: number | null) => { if (initialRef.current) { @@ -179,7 +183,7 @@ export function useClientPoint( // the dismissal touch point. const openCheck = isMouseLikePointerType(pointerType) ? floating : open; - const addListener = React.useCallback(() => { + const addListener = useCallback(() => { // Explicitly specified `x`/`y` coordinates shouldn't add a listener. if (!openCheck || !enabled || x != null || y != null) { return undefined; @@ -212,17 +216,17 @@ export function useClientPoint( return undefined; }, [openCheck, enabled, x, y, floating, dataRef, domReference, store, setReference]); - React.useEffect(() => { + useEffect(() => { return addListener(); }, [addListener, reactive]); - React.useEffect(() => { + useEffect(() => { if (enabled && !floating) { initialRef.current = false; } }, [enabled, floating]); - React.useEffect(() => { + useEffect(() => { if (!enabled && open) { initialRef.current = true; } diff --git a/packages/react/src/floating-ui-react/hooks/useDismiss.ts b/packages/react/src/floating-ui-react/hooks/useDismiss.ts index 85c1b1b3025..f77c2e59147 100644 --- a/packages/react/src/floating-ui-react/hooks/useDismiss.ts +++ b/packages/react/src/floating-ui-react/hooks/useDismiss.ts @@ -10,6 +10,8 @@ import { } from '@floating-ui/utils/dom'; import { Timeout, useTimeout } from '@base-ui-components/utils/useTimeout'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { contains, getDocument, @@ -156,10 +158,10 @@ export function useDismiss( ); const outsidePress = typeof outsidePressProp === 'function' ? outsidePressFn : outsidePressProp; - const endedOrStartedInsideRef = React.useRef(false); + const endedOrStartedInsideRef = useRef(false); const { escapeKey: escapeKeyBubbles, outsidePress: outsidePressBubbles } = normalizeProp(bubbles); - const touchStateRef = React.useRef<{ + const touchStateRef = useRef<{ startTime: number; startX: number; startY: number; @@ -175,8 +177,8 @@ export function useDismiss( dataRef.current.insideReactTree = false; }); - const isComposingRef = React.useRef(false); - const currentPointerTypeRef = React.useRef(''); + const isComposingRef = useRef(false); + const currentPointerTypeRef = useRef(''); const trackPointerType = useStableCallback((event: PointerEvent) => { currentPointerTypeRef.current = event.pointerType; @@ -533,7 +535,7 @@ export function useDismiss( target?.addEventListener(event.type, callback); }); - React.useEffect(() => { + useEffect(() => { if (!open || !enabled) { return undefined; } @@ -656,7 +658,7 @@ export function useDismiss( store, ]); - React.useEffect(clearInsideReactTree, [outsidePress, clearInsideReactTree]); + useEffect(clearInsideReactTree, [outsidePress, clearInsideReactTree]); const reference: ElementProps['reference'] = React.useMemo( () => ({ diff --git a/packages/react/src/floating-ui-react/hooks/useFloating.ts b/packages/react/src/floating-ui-react/hooks/useFloating.ts index cbc55fd1d15..4e421a1b09d 100644 --- a/packages/react/src/floating-ui-react/hooks/useFloating.ts +++ b/packages/react/src/floating-ui-react/hooks/useFloating.ts @@ -2,6 +2,9 @@ import * as React from 'react'; import { useFloating as usePosition, type VirtualElement } from '@floating-ui/react-dom'; import { isElement } from '@floating-ui/utils/dom'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; +import { useState } from '@base-ui-components/utils/useState'; import { useFloatingTree } from '../components/FloatingTree'; import type { @@ -30,9 +33,9 @@ export function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn domReference: rootContext.useState('domReferenceElement'), }; - const [positionReference, setPositionReferenceRaw] = React.useState(null); + const [positionReference, setPositionReferenceRaw] = useState(null); - const domReferenceRef = React.useRef | null>(null); + const domReferenceRef = useRef | null>(null); const tree = useFloatingTree(externalTree); @@ -51,7 +54,7 @@ export function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn }, }); - const setPositionReference = React.useCallback( + const setPositionReference = useCallback( (node: ReferenceType | null) => { const computedPositionReference = isElement(node) ? ({ @@ -69,8 +72,8 @@ export function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn ); const [localDomReference, setLocalDomReference] = - React.useState | null>(null); - const [localFloatingElement, setLocalFloatingElement] = React.useState(null); + useState | null>(null); + const [localFloatingElement, setLocalFloatingElement] = useState(null); rootContext.useSyncedValue('referenceElement', localDomReference); rootContext.useSyncedValue( 'domReferenceElement', @@ -78,7 +81,7 @@ export function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn ); rootContext.useSyncedValue('floatingElement', localFloatingElement); - const setReference = React.useCallback( + const setReference = useCallback( (node: ReferenceType | null) => { if (isElement(node) || node === null) { (domReferenceRef as React.MutableRefObject).current = node; @@ -101,7 +104,7 @@ export function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn [position.refs, setLocalDomReference], ); - const setFloating = React.useCallback( + const setFloating = useCallback( (node: HTMLElement | null) => { setLocalFloatingElement(node); position.refs.setFloating(node); diff --git a/packages/react/src/floating-ui-react/hooks/useFocus.ts b/packages/react/src/floating-ui-react/hooks/useFocus.ts index 1698b38dcb1..674317da9bc 100644 --- a/packages/react/src/floating-ui-react/hooks/useFocus.ts +++ b/packages/react/src/floating-ui-react/hooks/useFocus.ts @@ -2,6 +2,8 @@ import * as React from 'react'; import { getWindow, isElement, isHTMLElement } from '@floating-ui/utils/dom'; import { isMac, isSafari } from '@base-ui-components/utils/detectBrowser'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { activeElement, contains, @@ -48,11 +50,11 @@ export function useFocus( const { events, dataRef } = store.context; const { enabled = true, visibleOnly = true } = props; - const blockFocusRef = React.useRef(false); + const blockFocusRef = useRef(false); const timeout = useTimeout(); - const keyboardModalityRef = React.useRef(true); + const keyboardModalityRef = useRef(true); - React.useEffect(() => { + useEffect(() => { const domReference = store.select('domReferenceElement'); if (!enabled) { return undefined; @@ -98,7 +100,7 @@ export function useFocus( }; }, [store, enabled]); - React.useEffect(() => { + useEffect(() => { if (!enabled) { return undefined; } diff --git a/packages/react/src/floating-ui-react/hooks/useHover.ts b/packages/react/src/floating-ui-react/hooks/useHover.ts index c61dea9bdc9..e8b47e53801 100644 --- a/packages/react/src/floating-ui-react/hooks/useHover.ts +++ b/packages/react/src/floating-ui-react/hooks/useHover.ts @@ -4,6 +4,9 @@ import { useTimeout } from '@base-ui-components/utils/useTimeout'; import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { contains, getDocument, getTarget, isMouseLikePointerType } from '../utils'; import { useFloatingParentNodeId, useFloatingTree } from '../components/FloatingTree'; @@ -151,15 +154,15 @@ export function useHover( const delayRef = useValueAsRef(delay); const restMsRef = useValueAsRef(restMs); - const pointerTypeRef = React.useRef(undefined); - const interactedInsideRef = React.useRef(false); + const pointerTypeRef = useRef(undefined); + const interactedInsideRef = useRef(false); const timeout = useTimeout(); - const handlerRef = React.useRef<(event: MouseEvent) => void>(undefined); + const handlerRef = useRef<(event: MouseEvent) => void>(undefined); const restTimeout = useTimeout(); - const blockMouseMoveRef = React.useRef(true); - const performedPointerEventsMutationRef = React.useRef(false); - const unbindMouseMoveRef = React.useRef(() => {}); - const restTimeoutPendingRef = React.useRef(false); + const blockMouseMoveRef = useRef(true); + const performedPointerEventsMutationRef = useRef(false); + const unbindMouseMoveRef = useRef(() => {}); + const restTimeoutPendingRef = useRef(false); const isHoverOpen = useStableCallback(() => { const type = dataRef.current.openEvent?.type; @@ -178,7 +181,7 @@ export function useHover( // When closing before opening, clear the delay timeouts to cancel it // from showing. - React.useEffect(() => { + useEffect(() => { if (!enabled) { return undefined; } @@ -198,7 +201,7 @@ export function useHover( }; }, [enabled, events, timeout, restTimeout]); - React.useEffect(() => { + useEffect(() => { if (!enabled) { return undefined; } @@ -233,7 +236,7 @@ export function useHover( }; }, [floatingElement, open, store, enabled, handleCloseRef, isHoverOpen, isClickLikeOpenEvent]); - const closeWithDelay = React.useCallback( + const closeWithDelay = useCallback( (event: MouseEvent, runElseBranch = true) => { const closeDelay = getDelay(delayRef.current, 'close', pointerTypeRef.current); if (closeDelay && !handlerRef.current) { @@ -275,7 +278,7 @@ export function useHover( // Registering the mouse events on the reference directly to bypass React's // delegation system. If the cursor was on a disabled element and then entered // the reference (no gap), `mouseenter` doesn't fire in the delegation system. - React.useEffect(() => { + useEffect(() => { if (!enabled) { return undefined; } @@ -547,7 +550,7 @@ export function useHover( } }, [open, cleanupMouseMoveHandler, clearPointerEvents]); - React.useEffect(() => { + useEffect(() => { return () => { cleanupMouseMoveHandler(); timeout.clear(); @@ -556,7 +559,7 @@ export function useHover( }; }, [enabled, domReferenceElement, cleanupMouseMoveHandler, timeout, restTimeout]); - React.useEffect(() => { + useEffect(() => { return clearPointerEvents; }, [clearPointerEvents]); diff --git a/packages/react/src/floating-ui-react/hooks/useHoverFloatingInteraction.ts b/packages/react/src/floating-ui-react/hooks/useHoverFloatingInteraction.ts index 7f4780c815c..849771541b9 100644 --- a/packages/react/src/floating-ui-react/hooks/useHoverFloatingInteraction.ts +++ b/packages/react/src/floating-ui-react/hooks/useHoverFloatingInteraction.ts @@ -2,6 +2,8 @@ import * as React from 'react'; import { isElement } from '@floating-ui/utils/dom'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import type { FloatingContext, FloatingRootContext } from '../types'; import { getDocument, getTarget, isMouseLikePointerType } from '../utils'; @@ -79,7 +81,7 @@ export function useHoverFloatingInteraction( return type?.includes('mouse') && type !== 'mousedown'; }); - const closeWithDelay = React.useCallback( + const closeWithDelay = useCallback( (event: MouseEvent, runElseBranch = true) => { const closeDelay = getDelay(closeDelayProp, pointerTypeRef.current); if (closeDelay && !handlerRef.current) { @@ -135,13 +137,13 @@ export function useHoverFloatingInteraction( clearPointerEvents, ]); - React.useEffect(() => { + useEffect(() => { return () => { cleanupMouseMoveHandler(); }; }, [cleanupMouseMoveHandler]); - React.useEffect(() => { + useEffect(() => { return clearPointerEvents; }, [clearPointerEvents]); @@ -195,7 +197,7 @@ export function useHoverFloatingInteraction( performedPointerEventsMutationRef, ]); - React.useEffect(() => { + useEffect(() => { if (!enabled) { return undefined; } diff --git a/packages/react/src/floating-ui-react/hooks/useHoverInteractionSharedState.ts b/packages/react/src/floating-ui-react/hooks/useHoverInteractionSharedState.ts index 0c27efeb076..a0255fc07e8 100644 --- a/packages/react/src/floating-ui-react/hooks/useHoverInteractionSharedState.ts +++ b/packages/react/src/floating-ui-react/hooks/useHoverInteractionSharedState.ts @@ -1,5 +1,6 @@ import * as React from 'react'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; +import { useRef } from '@base-ui-components/utils/useRef'; import type { ContextData, FloatingRootContext, SafePolygonOptions } from '../types'; import { createAttribute } from '../utils/createAttribute'; @@ -32,16 +33,16 @@ type HoverContextData = ContextData & { export function useHoverInteractionSharedState( store: FloatingRootContext, ): HoverInteractionSharedState { - const pointerTypeRef = React.useRef(undefined); - const interactedInsideRef = React.useRef(false); - const handlerRef = React.useRef<((event: MouseEvent) => void) | undefined>(undefined); - const blockMouseMoveRef = React.useRef(true); - const performedPointerEventsMutationRef = React.useRef(false); - const unbindMouseMoveRef = React.useRef<() => void>(() => {}); - const restTimeoutPendingRef = React.useRef(false); + const pointerTypeRef = useRef(undefined); + const interactedInsideRef = useRef(false); + const handlerRef = useRef<((event: MouseEvent) => void) | undefined>(undefined); + const blockMouseMoveRef = useRef(true); + const performedPointerEventsMutationRef = useRef(false); + const unbindMouseMoveRef = useRef<() => void>(() => {}); + const restTimeoutPendingRef = useRef(false); const openChangeTimeout = useTimeout(); const restTimeout = useTimeout(); - const handleCloseOptionsRef = React.useRef(undefined); + const handleCloseOptionsRef = useRef(undefined); return React.useMemo(() => { const data = store.context.dataRef.current as HoverContextData; diff --git a/packages/react/src/floating-ui-react/hooks/useHoverReferenceInteraction.ts b/packages/react/src/floating-ui-react/hooks/useHoverReferenceInteraction.ts index 2ea8d116e82..60c8f2f6e15 100644 --- a/packages/react/src/floating-ui-react/hooks/useHoverReferenceInteraction.ts +++ b/packages/react/src/floating-ui-react/hooks/useHoverReferenceInteraction.ts @@ -3,6 +3,8 @@ import * as ReactDOM from 'react-dom'; import { isElement } from '@floating-ui/utils/dom'; import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import type { FloatingContext, FloatingRootContext } from '../types'; import { contains, getDocument, isMouseLikePointerType } from '../utils'; import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails'; @@ -90,7 +92,7 @@ export function useHoverReferenceInteraction( : false; }); - const closeWithDelay = React.useCallback( + const closeWithDelay = useCallback( (event: MouseEvent, runElseBranch = true) => { const closeDelay = getDelay(delayRef.current, 'close', pointerTypeRef.current); if (closeDelay && !closeHandlerRef.current) { @@ -121,7 +123,7 @@ export function useHoverReferenceInteraction( // When closing before opening, clear the delay timeouts to cancel it // from showing. - React.useEffect(() => { + useEffect(() => { if (!enabled) { return undefined; } @@ -169,7 +171,7 @@ export function useHoverReferenceInteraction( })(event); }); - React.useEffect(() => { + useEffect(() => { if (!enabled) { return undefined; } diff --git a/packages/react/src/floating-ui-react/hooks/useInteractions.ts b/packages/react/src/floating-ui-react/hooks/useInteractions.ts index 9bb23a30a98..daa7c2041a4 100644 --- a/packages/react/src/floating-ui-react/hooks/useInteractions.ts +++ b/packages/react/src/floating-ui-react/hooks/useInteractions.ts @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import type { ElementProps } from '../types'; import { ACTIVE_KEY, FOCUSABLE_ATTRIBUTE, SELECTED_KEY } from '../utils/constants'; @@ -29,26 +30,26 @@ export function useInteractions(propsList: Array = []): Use const itemDeps = propsList.map((key) => key?.item); const triggerDeps = propsList.map((key) => key?.trigger); - const getReferenceProps = React.useCallback( + const getReferenceProps = useCallback( (userProps?: React.HTMLProps) => mergeProps(userProps, propsList, 'reference'), // eslint-disable-next-line react-hooks/exhaustive-deps referenceDeps, ); - const getFloatingProps = React.useCallback( + const getFloatingProps = useCallback( (userProps?: React.HTMLProps) => mergeProps(userProps, propsList, 'floating'), // eslint-disable-next-line react-hooks/exhaustive-deps floatingDeps, ); - const getItemProps = React.useCallback( + const getItemProps = useCallback( (userProps?: Omit, 'selected' | 'active'> & ExtendedUserProps) => mergeProps(userProps, propsList, 'item'), // eslint-disable-next-line react-hooks/exhaustive-deps itemDeps, ); - const getTriggerProps = React.useCallback( + const getTriggerProps = useCallback( (userProps?: React.HTMLProps) => mergeProps(userProps, propsList, 'trigger'), // eslint-disable-next-line react-hooks/exhaustive-deps triggerDeps, diff --git a/packages/react/src/floating-ui-react/hooks/useListNavigation.ts b/packages/react/src/floating-ui-react/hooks/useListNavigation.ts index d670f424640..5c88d57bb25 100644 --- a/packages/react/src/floating-ui-react/hooks/useListNavigation.ts +++ b/packages/react/src/floating-ui-react/hooks/useListNavigation.ts @@ -3,6 +3,8 @@ import { isHTMLElement } from '@floating-ui/utils/dom'; import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { activeElement, contains, @@ -303,20 +305,20 @@ export function useListNavigation( const typeableComboboxReference = isTypeableCombobox(domReferenceElement); - const focusItemOnOpenRef = React.useRef(focusItemOnOpen); - const indexRef = React.useRef(selectedIndex ?? -1); - const keyRef = React.useRef(null); - const isPointerModalityRef = React.useRef(true); + const focusItemOnOpenRef = useRef(focusItemOnOpen); + const indexRef = useRef(selectedIndex ?? -1); + const keyRef = useRef(null); + const isPointerModalityRef = useRef(true); const onNavigate = useStableCallback((event?: React.SyntheticEvent) => { onNavigateProp(indexRef.current === -1 ? null : indexRef.current, event); }); - const previousOnNavigateRef = React.useRef(onNavigate); - const previousMountedRef = React.useRef(!!floatingElement); - const previousOpenRef = React.useRef(open); - const forceSyncFocusRef = React.useRef(false); - const forceScrollIntoViewRef = React.useRef(false); + const previousOnNavigateRef = useRef(onNavigate); + const previousMountedRef = useRef(!!floatingElement); + const previousOpenRef = useRef(open); + const forceSyncFocusRef = useRef(false); + const forceScrollIntoViewRef = useRef(false); const disabledIndicesRef = useValueAsRef(disabledIndices); const latestOpenRef = useValueAsRef(open); @@ -565,7 +567,7 @@ export function useListNavigation( return itemProps; }, [latestOpenRef, floatingFocusElementRef, focusItemOnHover, listRef, onNavigate, virtual]); - const getParentOrientation = React.useCallback(() => { + const getParentOrientation = useCallback(() => { return ( parentOrientation ?? (tree?.nodesRef.current.find((node) => node.id === parentId)?.context?.dataRef?.current diff --git a/packages/react/src/floating-ui-react/hooks/useRole.ts b/packages/react/src/floating-ui-react/hooks/useRole.ts index a4745b20d77..efa46e81f79 100644 --- a/packages/react/src/floating-ui-react/hooks/useRole.ts +++ b/packages/react/src/floating-ui-react/hooks/useRole.ts @@ -1,5 +1,6 @@ import * as React from 'react'; import { useId } from '@base-ui-components/utils/useId'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { getFloatingFocusElement } from '../utils'; import { useFloatingParentNodeId } from '../components/FloatingTree'; import type { ElementProps, FloatingContext, FloatingRootContext } from '../types'; @@ -107,7 +108,7 @@ export function useRole( }; }, [ariaRole, floatingId, referenceId, role]); - const item: ElementProps['item'] = React.useCallback( + const item: ElementProps['item'] = useCallback( ({ active, selected }: ExtendedUserProps) => { const commonProps = { role: 'option', diff --git a/packages/react/src/floating-ui-react/hooks/useTypeahead.ts b/packages/react/src/floating-ui-react/hooks/useTypeahead.ts index 4c5386c9bfc..d75d1d1b8e8 100644 --- a/packages/react/src/floating-ui-react/hooks/useTypeahead.ts +++ b/packages/react/src/floating-ui-react/hooks/useTypeahead.ts @@ -2,6 +2,7 @@ import * as React from 'react'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; +import { useRef } from '@base-ui-components/utils/useRef'; import { stopEvent } from '../utils'; import type { ElementProps, FloatingContext, FloatingRootContext } from '../types'; @@ -82,9 +83,9 @@ export function useTypeahead( } = props; const timeout = useTimeout(); - const stringRef = React.useRef(''); - const prevIndexRef = React.useRef(selectedIndex ?? activeIndex ?? -1); - const matchIndexRef = React.useRef(null); + const stringRef = useRef(''); + const prevIndexRef = useRef(selectedIndex ?? activeIndex ?? -1); + const matchIndexRef = useRef(null); useIsoLayoutEffect(() => { if (open) { diff --git a/packages/react/src/form/Form.tsx b/packages/react/src/form/Form.tsx index 5065956dee4..568c9d807c7 100644 --- a/packages/react/src/form/Form.tsx +++ b/packages/react/src/form/Form.tsx @@ -1,6 +1,9 @@ 'use client'; import * as React from 'react'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { createGenericEventDetails, type BaseUIGenericEventDetails, @@ -31,11 +34,11 @@ export const Form = React.forwardRef(function Form< ...elementProps } = componentProps; - const formRef = React.useRef({ + const formRef = useRef({ fields: new Map(), }); - const submittedRef = React.useRef(false); - const submitAttemptedRef = React.useRef(false); + const submittedRef = useRef(false); + const submitAttemptedRef = useRef(false); const focusControl = useStableCallback((control: HTMLElement | null) => { if (!control) { @@ -47,13 +50,13 @@ export const Form = React.forwardRef(function Form< } }); - const [errors, setErrors] = React.useState(externalErrors); + const [errors, setErrors] = useState(externalErrors); useValueChanged(externalErrors, () => { setErrors(externalErrors); }); - React.useEffect(() => { + useEffect(() => { if (!submittedRef.current) { return; } diff --git a/packages/react/src/labelable-provider/LabelableProvider.tsx b/packages/react/src/labelable-provider/LabelableProvider.tsx index 34951ab5ff7..74cd9667886 100644 --- a/packages/react/src/labelable-provider/LabelableProvider.tsx +++ b/packages/react/src/labelable-provider/LabelableProvider.tsx @@ -1,5 +1,7 @@ 'use client'; import * as React from 'react'; +import { useCallback } from '@base-ui-components/utils/useCallback'; +import { useState } from '@base-ui-components/utils/useState'; import { mergeProps } from '../merge-props'; import { HTMLProps } from '../utils/types'; import { useBaseUiId } from '../utils/useBaseUiId'; @@ -13,15 +15,15 @@ export const LabelableProvider: React.FC = function Lab ) { const defaultId = useBaseUiId(); - const [controlId, setControlId] = React.useState( + const [controlId, setControlId] = useState( props.initialControlId === undefined ? defaultId : props.initialControlId, ); - const [labelId, setLabelId] = React.useState(undefined); - const [messageIds, setMessageIds] = React.useState([]); + const [labelId, setLabelId] = useState(undefined); + const [messageIds, setMessageIds] = useState([]); const { messageIds: parentMessageIds } = useLabelableContext(); - const getDescriptionProps = React.useCallback( + const getDescriptionProps = useCallback( (externalProps: HTMLProps) => { return mergeProps( { 'aria-describedby': parentMessageIds.concat(messageIds).join(' ') || undefined }, diff --git a/packages/react/src/menu/checkbox-item-indicator/MenuCheckboxItemIndicator.tsx b/packages/react/src/menu/checkbox-item-indicator/MenuCheckboxItemIndicator.tsx index b8315601d4f..e01be6c99b2 100644 --- a/packages/react/src/menu/checkbox-item-indicator/MenuCheckboxItemIndicator.tsx +++ b/packages/react/src/menu/checkbox-item-indicator/MenuCheckboxItemIndicator.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useRef } from '@base-ui-components/utils/useRef'; import { useMenuCheckboxItemContext } from '../checkbox-item/MenuCheckboxItemContext'; import { useRenderElement } from '../../utils/useRenderElement'; import { BaseUIComponentProps } from '../../utils/types'; @@ -21,7 +22,7 @@ export const MenuCheckboxItemIndicator = React.forwardRef(function MenuCheckboxI const item = useMenuCheckboxItemContext(); - const indicatorRef = React.useRef(null); + const indicatorRef = useRef(null); const { transitionStatus, setMounted } = useTransitionStatus(item.checked); diff --git a/packages/react/src/menu/group/MenuGroup.tsx b/packages/react/src/menu/group/MenuGroup.tsx index e1278254f1e..9d697f5ca53 100644 --- a/packages/react/src/menu/group/MenuGroup.tsx +++ b/packages/react/src/menu/group/MenuGroup.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useState } from '@base-ui-components/utils/useState'; import { BaseUIComponentProps } from '../../utils/types'; import { useRenderElement } from '../../utils/useRenderElement'; import { MenuGroupContext } from './MenuGroupContext'; @@ -16,7 +17,7 @@ export const MenuGroup = React.forwardRef(function MenuGroup( ) { const { render, className, ...elementProps } = componentProps; - const [labelId, setLabelId] = React.useState(undefined); + const [labelId, setLabelId] = useState(undefined); const context = React.useMemo(() => ({ setLabelId }), [setLabelId]); diff --git a/packages/react/src/menu/item/useMenuItem.ts b/packages/react/src/menu/item/useMenuItem.ts index 32f7d51bde6..4d7ff991548 100644 --- a/packages/react/src/menu/item/useMenuItem.ts +++ b/packages/react/src/menu/item/useMenuItem.ts @@ -1,6 +1,8 @@ 'use client'; import * as React from 'react'; import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { useButton } from '../../use-button'; import { mergeProps } from '../../merge-props'; import { HTMLProps, BaseUIEvent } from '../../utils/types'; @@ -24,7 +26,7 @@ export function useMenuItem(params: useMenuItem.Parameters): useMenuItem.ReturnV nodeId, } = params; - const itemRef = React.useRef(null); + const itemRef = useRef(null); const contextMenuContext = useContextMenuRootContext(true); const isContextMenu = contextMenuContext !== undefined; const { events: menuEvents } = store.useState('floatingTreeRoot'); @@ -35,7 +37,7 @@ export function useMenuItem(params: useMenuItem.Parameters): useMenuItem.ReturnV native: nativeButton, }); - const getItemProps = React.useCallback( + const getItemProps = useCallback( (externalProps?: HTMLProps): HTMLProps => { return mergeProps<'div'>( { diff --git a/packages/react/src/menu/popup/MenuPopup.tsx b/packages/react/src/menu/popup/MenuPopup.tsx index 825691c3abd..8754f261ae4 100644 --- a/packages/react/src/menu/popup/MenuPopup.tsx +++ b/packages/react/src/menu/popup/MenuPopup.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useEffect } from '@base-ui-components/utils/useEffect'; import type { InteractionType } from '@base-ui-components/utils/useEnhancedClickHandler'; import { FloatingFocusManager, useHoverFloatingInteraction } from '../../floating-ui-react'; import { useMenuRootContext } from '../root/MenuRootContext'; @@ -64,7 +65,7 @@ export const MenuPopup = React.forwardRef(function MenuPopup( }, }); - React.useEffect(() => { + useEffect(() => { function handleClose(event: { domEvent: Event | undefined; reason: MenuRoot.ChangeEventReason; diff --git a/packages/react/src/menu/positioner/MenuPositioner.tsx b/packages/react/src/menu/positioner/MenuPositioner.tsx index 7a9ed27590c..1a4ea59781b 100644 --- a/packages/react/src/menu/positioner/MenuPositioner.tsx +++ b/packages/react/src/menu/positioner/MenuPositioner.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import { inertValue } from '@base-ui-components/utils/inertValue'; +import { useEffect } from '@base-ui-components/utils/useEffect'; import { FloatingNode } from '../../floating-ui-react'; import { MenuPositionerContext } from './MenuPositionerContext'; import { useMenuRootContext } from '../root/MenuRootContext'; @@ -125,7 +126,7 @@ export const MenuPositioner = React.forwardRef(function MenuPositioner( }; }, [open, mounted, positioner.positionerStyles]); - React.useEffect(() => { + useEffect(() => { function onMenuOpenChange(details: MenuOpenEventDetails) { if (details.open) { if (details.parentNodeId === floatingNodeId) { @@ -155,7 +156,7 @@ export const MenuPositioner = React.forwardRef(function MenuPositioner( }; }, [store, floatingTreeRoot.events, floatingNodeId]); - React.useEffect(() => { + useEffect(() => { if (store.select('floatingParentNodeId') == null) { return undefined; } @@ -177,7 +178,7 @@ export const MenuPositioner = React.forwardRef(function MenuPositioner( }, [floatingTreeRoot.events, store]); // Close unrelated child submenus when hovering a different item in the parent menu. - React.useEffect(() => { + useEffect(() => { function onItemHover(event: { nodeId: string | undefined; target: Element | null }) { // If an item within our parent menu is hovered, and this menu's trigger is not that item, // close this submenu. This ensures hovering a different item in the parent closes other branches. @@ -196,7 +197,7 @@ export const MenuPositioner = React.forwardRef(function MenuPositioner( }; }, [floatingTreeRoot.events, open, triggerElement, store]); - React.useEffect(() => { + useEffect(() => { const eventDetails: MenuOpenEventDetails = { open, nodeId: floatingNodeId, diff --git a/packages/react/src/menu/radio-item-indicator/MenuRadioItemIndicator.tsx b/packages/react/src/menu/radio-item-indicator/MenuRadioItemIndicator.tsx index 5a0efe1aea6..687ff788e14 100644 --- a/packages/react/src/menu/radio-item-indicator/MenuRadioItemIndicator.tsx +++ b/packages/react/src/menu/radio-item-indicator/MenuRadioItemIndicator.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useRef } from '@base-ui-components/utils/useRef'; import { useMenuRadioItemContext } from '../radio-item/MenuRadioItemContext'; import { useRenderElement } from '../../utils/useRenderElement'; import { BaseUIComponentProps } from '../../utils/types'; @@ -21,7 +22,7 @@ export const MenuRadioItemIndicator = React.forwardRef(function MenuRadioItemInd const item = useMenuRadioItemContext(); - const indicatorRef = React.useRef(null); + const indicatorRef = useRef(null); const { transitionStatus, setMounted } = useTransitionStatus(item.checked); diff --git a/packages/react/src/menu/root/MenuRoot.tsx b/packages/react/src/menu/root/MenuRoot.tsx index a00e6b78054..ee146d8bf48 100644 --- a/packages/react/src/menu/root/MenuRoot.tsx +++ b/packages/react/src/menu/root/MenuRoot.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; +import * as fastHooks from '@base-ui-components/utils/fastHooks'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useId } from '@base-ui-components/utils/useId'; @@ -8,6 +9,9 @@ import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect import { useAnimationFrame } from '@base-ui-components/utils/useAnimationFrame'; import { useScrollLock } from '@base-ui-components/utils/useScrollLock'; import { EMPTY_ARRAY } from '@base-ui-components/utils/empty'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { FloatingEvents, FloatingTree, @@ -51,7 +55,9 @@ import { useMenuSubmenuRootContext } from '../submenu-root/MenuSubmenuRootContex * * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu) */ -export function MenuRoot(props: MenuRoot.Props) { +export const MenuRoot = fastHooks.createComponent(function MenuRoot( + props: MenuRoot.Props, +) { const { children, open: openProp, @@ -156,7 +162,7 @@ export function MenuRoot(props: MenuRoot.Props) { const payload = store.useState('payload') as Payload | undefined; const floatingParentNodeId = store.useState('floatingParentNodeId'); - const openEventRef = React.useRef(null); + const openEventRef = useRef(null); const nested = floatingParentNodeId != null; @@ -188,10 +194,10 @@ export function MenuRoot(props: MenuRoot.Props) { resetOpenInteractionType(); }); - const allowOutsidePressDismissalRef = React.useRef(parent.type !== 'context-menu'); + const allowOutsidePressDismissalRef = useRef(parent.type !== 'context-menu'); const allowOutsidePressDismissalTimeout = useTimeout(); - React.useEffect(() => { + useEffect(() => { if (!open) { openEventRef.current = null; } @@ -225,7 +231,7 @@ export function MenuRoot(props: MenuRoot.Props) { } }, [open, hoverEnabled, store]); - const allowTouchToCloseRef = React.useRef(true); + const allowTouchToCloseRef = useRef(true); const allowTouchToCloseTimeout = useTimeout(); const setOpen = useStableCallback( @@ -344,7 +350,7 @@ export function MenuRoot(props: MenuRoot.Props) { }, ); - const createMenuEventDetails = React.useCallback( + const createMenuEventDetails = useCallback( (reason: MenuRoot.ChangeEventReason) => { const details: MenuRoot.ChangeEventDetails = createChangeEventDetails(reason) as MenuRoot.ChangeEventDetails; @@ -357,7 +363,7 @@ export function MenuRoot(props: MenuRoot.Props) { [store], ); - const handleImperativeClose = React.useCallback(() => { + const handleImperativeClose = useCallback(() => { store.setOpen(false, createMenuEventDetails(REASONS.imperativeAction)); }, [store, createMenuEventDetails]); @@ -387,7 +393,7 @@ export function MenuRoot(props: MenuRoot.Props) { floatingEvents = floatingRootContext.context.events; - React.useEffect(() => { + useEffect(() => { const handleSetOpenEvent = ({ open: nextOpen, eventDetails, @@ -422,7 +428,7 @@ export function MenuRoot(props: MenuRoot.Props) { const direction = useDirection(); - const setActiveIndex = React.useCallback( + const setActiveIndex = useCallback( (index: number | null) => { if (store.select('activeIndex') === index) { return; @@ -447,7 +453,7 @@ export function MenuRoot(props: MenuRoot.Props) { externalTree: nested ? floatingTreeRoot : undefined, }); - const onTypingChange = React.useCallback( + const onTypingChange = useCallback( (nextTyping: boolean) => { store.context.typingRef.current = nextTyping; }, @@ -560,7 +566,7 @@ export function MenuRoot(props: MenuRoot.Props) { } return content; -} +}); export interface MenuRootProps { /** diff --git a/packages/react/src/menu/submenu-trigger/MenuSubmenuTrigger.tsx b/packages/react/src/menu/submenu-trigger/MenuSubmenuTrigger.tsx index 37ab97dd54d..be929af2815 100644 --- a/packages/react/src/menu/submenu-trigger/MenuSubmenuTrigger.tsx +++ b/packages/react/src/menu/submenu-trigger/MenuSubmenuTrigger.tsx @@ -1,5 +1,7 @@ 'use client'; import * as React from 'react'; +import { useCallback } from '@base-ui-components/utils/useCallback'; +import { useState } from '@base-ui-components/utils/useState'; import { safePolygon, useClick, @@ -51,7 +53,7 @@ export const MenuSubmenuTrigger = React.forwardRef(function SubmenuTriggerCompon const floatingTreeRoot = store.useState('floatingTreeRoot'); const baseRegisterTrigger = useTriggerRegistration(thisTriggerId, store); - const registerTrigger = React.useCallback( + const registerTrigger = useCallback( (element: Element | null) => { const cleanup = baseRegisterTrigger(element); @@ -68,7 +70,7 @@ export const MenuSubmenuTrigger = React.forwardRef(function SubmenuTriggerCompon [baseRegisterTrigger, closeDelay, store, thisTriggerId], ); - const [triggerElement, setTriggerElement] = React.useState(null); + const [triggerElement, setTriggerElement] = useState(null); const submenuRootContext = useMenuSubmenuRootContext(); if (!submenuRootContext?.parentMenu) { diff --git a/packages/react/src/menu/trigger/MenuTrigger.tsx b/packages/react/src/menu/trigger/MenuTrigger.tsx index 2a1d70be155..aa26e38b628 100644 --- a/packages/react/src/menu/trigger/MenuTrigger.tsx +++ b/packages/react/src/menu/trigger/MenuTrigger.tsx @@ -2,11 +2,15 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { FocusableElement } from 'tabbable'; +import * as fastHooks from '@base-ui-components/utils/fastHooks'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; import { ownerDocument } from '@base-ui-components/utils/owner'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { EMPTY_OBJECT } from '@base-ui-components/utils/empty'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { safePolygon, useClick, @@ -54,7 +58,7 @@ const BOUNDARY_OFFSET = 2; * * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu) */ -export const MenuTrigger = React.forwardRef(function MenuTrigger( +export const MenuTrigger = fastHooks.createComponent(function MenuTrigger( componentProps: MenuTrigger.Props, forwardedRef: React.ForwardedRef, ) { @@ -85,7 +89,7 @@ export const MenuTrigger = React.forwardRef(function MenuTrigger( const floatingRootContext = store.useState('floatingRootContext'); const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId); - const [triggerElement, setTriggerElement] = React.useState(null); + const [triggerElement, setTriggerElement] = useState(null); const parent = useMenuParent(); const compositeRootContext = useCompositeRootContext(true); @@ -121,13 +125,13 @@ export const MenuTrigger = React.forwardRef(function MenuTrigger( native: nativeButton, }); - React.useEffect(() => { + useEffect(() => { if (!isOpenedByThisTrigger && parent.type === undefined) { store.context.allowMouseUpTriggerRef.current = false; } }, [store, isOpenedByThisTrigger, parent.type]); - const triggerRef = React.useRef(null); + const triggerRef = useRef(null); const allowMouseUpTriggerTimeout = useTimeout(); const handleDocumentMouseUp = useStableCallback((mouseEvent: MouseEvent) => { @@ -166,7 +170,7 @@ export const MenuTrigger = React.forwardRef(function MenuTrigger( floatingTreeRoot.events.emit('close', { domEvent: mouseEvent, reason: REASONS.cancelOpen }); }); - React.useEffect(() => { + useEffect(() => { if (isOpenedByThisTrigger && store.select('lastOpenChangeReason') === REASONS.triggerHover) { const doc = ownerDocument(triggerRef.current); doc.addEventListener('mouseup', handleDocumentMouseUp, { once: true }); @@ -259,7 +263,7 @@ export const MenuTrigger = React.forwardRef(function MenuTrigger( getButtonProps, ]; - const preFocusGuardRef = React.useRef(null); + const preFocusGuardRef = useRef(null); const handlePreFocusGuardFocus = useStableCallback((event: React.FocusEvent) => { ReactDOM.flushSync(() => { @@ -419,7 +423,7 @@ export namespace MenuTrigger { */ function useStickIfOpen(open: boolean, openReason: string | null) { const stickIfOpenTimeout = useTimeout(); - const [stickIfOpen, setStickIfOpen] = React.useState(false); + const [stickIfOpen, setStickIfOpen] = useState(false); useIsoLayoutEffect(() => { if (open && openReason === 'trigger-hover') { // Only allow "patient" clicks to close the menu if it's open. diff --git a/packages/react/src/menubar/Menubar.tsx b/packages/react/src/menubar/Menubar.tsx index 8e77d16350c..7a50ff4f86e 100644 --- a/packages/react/src/menubar/Menubar.tsx +++ b/packages/react/src/menubar/Menubar.tsx @@ -1,6 +1,9 @@ 'use client'; import * as React from 'react'; import { useScrollLock } from '@base-ui-components/utils/useScrollLock'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { FloatingNode, FloatingTree, @@ -44,8 +47,8 @@ export const Menubar = React.forwardRef(function Menubar( ...elementProps } = props; - const [contentElement, setContentElement] = React.useState(null); - const [hasSubmenuOpen, setHasSubmenuOpen] = React.useState(false); + const [contentElement, setContentElement] = useState(null); + const [hasSubmenuOpen, setHasSubmenuOpen] = useState(false); const { openMethod, @@ -53,7 +56,7 @@ export const Menubar = React.forwardRef(function Menubar( reset: resetOpenInteractionType, } = useOpenInteractionType(hasSubmenuOpen); - React.useEffect(() => { + useEffect(() => { if (!hasSubmenuOpen) { resetOpenInteractionType(); } @@ -72,8 +75,8 @@ export const Menubar = React.forwardRef(function Menubar( [orientation, modal, hasSubmenuOpen], ); - const contentRef = React.useRef(null); - const allowMouseUpTriggerRef = React.useRef(false); + const contentRef = useRef(null); + const allowMouseUpTriggerRef = useRef(false); const context: MenubarContext = React.useMemo( () => ({ @@ -116,7 +119,7 @@ function MenubarContent(props: React.PropsWithChildren<{}>) { const { events: menuEvents } = useFloatingTree()!; const rootContext = useMenubarContext(); - React.useEffect(() => { + useEffect(() => { function onSubmenuOpenChange(details: MenuOpenEventDetails) { if (!details.nodeId || details.parentNodeId !== nodeId) { return; diff --git a/packages/react/src/meter/root/MeterRoot.tsx b/packages/react/src/meter/root/MeterRoot.tsx index 95a7ef74f55..caedfc564c7 100644 --- a/packages/react/src/meter/root/MeterRoot.tsx +++ b/packages/react/src/meter/root/MeterRoot.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useState } from '@base-ui-components/utils/useState'; import { MeterRootContext } from './MeterRootContext'; import { BaseUIComponentProps, HTMLProps } from '../../utils/types'; import { formatNumber } from '../../utils/formatNumber'; @@ -39,7 +40,7 @@ export const MeterRoot = React.forwardRef(function MeterRoot( ...elementProps } = componentProps; - const [labelId, setLabelId] = React.useState(); + const [labelId, setLabelId] = useState(); const formattedValue = formatValue(valueProp, locale, format); let ariaValuetext = `${valueProp}%`; diff --git a/packages/react/src/navigation-menu/content/NavigationMenuContent.tsx b/packages/react/src/navigation-menu/content/NavigationMenuContent.tsx index 12bcf8c3a46..479046d9372 100644 --- a/packages/react/src/navigation-menu/content/NavigationMenuContent.tsx +++ b/packages/react/src/navigation-menu/content/NavigationMenuContent.tsx @@ -2,6 +2,9 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { inertValue } from '@base-ui-components/utils/inertValue'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; +import { useState } from '@base-ui-components/utils/useState'; import { FloatingNode } from '../../floating-ui-react'; import { contains, getTarget } from '../../floating-ui-react/utils'; import type { BaseUIComponentProps, HTMLProps } from '../../utils/types'; @@ -56,9 +59,9 @@ export const NavigationMenuContent = React.forwardRef(function NavigationMenuCon const open = popupMounted && value === itemValue; - const ref = React.useRef(null); + const ref = useRef(null); - const [focusInside, setFocusInside] = React.useState(false); + const [focusInside, setFocusInside] = useState(false); const { mounted, setMounted, transitionStatus } = useTransitionStatus(open); @@ -81,7 +84,7 @@ export const NavigationMenuContent = React.forwardRef(function NavigationMenuCon [open, transitionStatus, activationDirection], ); - const handleCurrentContentRef = React.useCallback( + const handleCurrentContentRef = useCallback( (node: HTMLDivElement | null) => { if (node) { currentContentRef.current = node; diff --git a/packages/react/src/navigation-menu/positioner/NavigationMenuPositioner.tsx b/packages/react/src/navigation-menu/positioner/NavigationMenuPositioner.tsx index 41956c11caa..a2173cda80a 100644 --- a/packages/react/src/navigation-menu/positioner/NavigationMenuPositioner.tsx +++ b/packages/react/src/navigation-menu/positioner/NavigationMenuPositioner.tsx @@ -3,6 +3,9 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { ownerWindow } from '@base-ui-components/utils/owner'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { disableFocusInside, enableFocusInside, @@ -68,13 +71,13 @@ export const NavigationMenuPositioner = React.forwardRef(function NavigationMenu const resizeTimeout = useTimeout(); - const [instant, setInstant] = React.useState(false); + const [instant, setInstant] = useState(false); - const positionerRef = React.useRef(null); - const prevTriggerElementRef = React.useRef(null); + const positionerRef = useRef(null); + const prevTriggerElementRef = useRef(null); // https://codesandbox.io/s/tabbable-portal-f4tng?file=/src/TabbablePortal.tsx - React.useEffect(() => { + useEffect(() => { if (!positionerElement) { return undefined; } @@ -152,7 +155,7 @@ export const NavigationMenuPositioner = React.forwardRef(function NavigationMenu [open, positioning.side, positioning.align, positioning.anchorHidden, instant], ); - React.useEffect(() => { + useEffect(() => { if (!open) { return undefined; } diff --git a/packages/react/src/navigation-menu/root/NavigationMenuRoot.tsx b/packages/react/src/navigation-menu/root/NavigationMenuRoot.tsx index 56729423611..377f0b895ac 100644 --- a/packages/react/src/navigation-menu/root/NavigationMenuRoot.tsx +++ b/packages/react/src/navigation-menu/root/NavigationMenuRoot.tsx @@ -4,6 +4,9 @@ import { isHTMLElement } from '@floating-ui/utils/dom'; import { useControlled } from '@base-ui-components/utils/useControlled'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { ownerDocument } from '@base-ui-components/utils/owner'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { FloatingTree, useFloatingNodeId, @@ -63,32 +66,32 @@ export const NavigationMenuRoot = React.forwardRef(function NavigationMenuRoot( // Derive open state from value being non-nullish const open = value != null; - const closeReasonRef = React.useRef(undefined); - const rootRef = React.useRef(null); + const closeReasonRef = useRef(undefined); + const rootRef = useRef(null); - const [positionerElement, setPositionerElement] = React.useState(null); - const [popupElement, setPopupElement] = React.useState(null); - const [viewportElement, setViewportElement] = React.useState(null); - const [viewportTargetElement, setViewportTargetElement] = React.useState( + const [positionerElement, setPositionerElement] = useState(null); + const [popupElement, setPopupElement] = useState(null); + const [viewportElement, setViewportElement] = useState(null); + const [viewportTargetElement, setViewportTargetElement] = useState( null, ); const [activationDirection, setActivationDirection] = - React.useState(null); - const [floatingRootContext, setFloatingRootContext] = React.useState< + useState(null); + const [floatingRootContext, setFloatingRootContext] = useState< FloatingRootContext | undefined >(undefined); - const [viewportInert, setViewportInert] = React.useState(false); + const [viewportInert, setViewportInert] = useState(false); - const prevTriggerElementRef = React.useRef(null); - const currentContentRef = React.useRef(null); - const beforeInsideRef = React.useRef(null); - const afterInsideRef = React.useRef(null); - const beforeOutsideRef = React.useRef(null); - const afterOutsideRef = React.useRef(null); + const prevTriggerElementRef = useRef(null); + const currentContentRef = useRef(null); + const beforeInsideRef = useRef(null); + const afterInsideRef = useRef(null); + const beforeOutsideRef = useRef(null); + const afterOutsideRef = useRef(null); const { mounted, setMounted, transitionStatus } = useTransitionStatus(open); - React.useEffect(() => { + useEffect(() => { setViewportInert(false); }, [value]); diff --git a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx index 0b8ab47b1f7..a0f160a8a4c 100644 --- a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx +++ b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx @@ -7,6 +7,9 @@ import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect import { useTimeout } from '@base-ui-components/utils/useTimeout'; import { useAnimationFrame } from '@base-ui-components/utils/useAnimationFrame'; import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { safePolygon, useClick, @@ -91,13 +94,13 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri const sizeFrame1 = useAnimationFrame(); const sizeFrame2 = useAnimationFrame(); - const [triggerElement, setTriggerElement] = React.useState(null); - const [stickIfOpen, setStickIfOpen] = React.useState(true); - const [pointerType, setPointerType] = React.useState<'mouse' | 'touch' | 'pen' | ''>(''); + const [triggerElement, setTriggerElement] = useState(null); + const [stickIfOpen, setStickIfOpen] = useState(true); + const [pointerType, setPointerType] = useState<'mouse' | 'touch' | 'pen' | ''>(''); - const allowFocusRef = React.useRef(false); - const prevSizeRef = React.useRef(DEFAULT_SIZE); - const animationAbortControllerRef = React.useRef(null); + const allowFocusRef = useRef(false); + const prevSizeRef = useRef(DEFAULT_SIZE); + const animationAbortControllerRef = useRef(null); const isActiveItem = open && value === itemValue; const isActiveItemRef = useValueAsRef(isActiveItem); @@ -105,7 +108,7 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri const runOnceAnimationsFinish = useAnimationsFinished(popupElement); - React.useEffect(() => { + useEffect(() => { animationAbortControllerRef.current?.abort(); }, [isActiveItem]); @@ -159,7 +162,7 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri }); }); - React.useEffect(() => { + useEffect(() => { if (!open) { stickIfOpenTimeout.clear(); sizeFrame1.cancel(); @@ -167,13 +170,13 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri } }, [stickIfOpenTimeout, open, sizeFrame1, sizeFrame2]); - React.useEffect(() => { + useEffect(() => { if (!mounted) { prevSizeRef.current = DEFAULT_SIZE; } }, [mounted]); - React.useEffect(() => { + useEffect(() => { if (!popupElement || typeof ResizeObserver !== 'function') { return undefined; } @@ -193,7 +196,7 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri }; }, [popupElement]); - React.useEffect(() => { + useEffect(() => { if (!popupElement || !isActiveItem || typeof MutationObserver !== 'function') { return undefined; } @@ -214,7 +217,7 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri }; }, [popupElement, positionerElement, isActiveItem, handleValueChange]); - React.useEffect(() => { + useEffect(() => { if (isActiveItem && open && popupElement && allowFocusRef.current) { allowFocusRef.current = false; focusFrame.request(() => { diff --git a/packages/react/src/number-field/input/NumberFieldInput.tsx b/packages/react/src/number-field/input/NumberFieldInput.tsx index c08d83c1156..eb3ccdde017 100644 --- a/packages/react/src/number-field/input/NumberFieldInput.tsx +++ b/packages/react/src/number-field/input/NumberFieldInput.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useRef } from '@base-ui-components/utils/useRef'; import { stopEvent } from '../../floating-ui-react/utils'; import { useNumberFieldRootContext } from '../root/NumberFieldRootContext'; import type { BaseUIComponentProps } from '../../utils/types'; @@ -91,8 +92,8 @@ export const NumberFieldInput = React.forwardRef(function NumberFieldInput( useFieldRootContext(); const { labelId } = useLabelableContext(); - const hasTouchedInputRef = React.useRef(false); - const blockRevalidationRef = React.useRef(false); + const hasTouchedInputRef = useRef(false); + const blockRevalidationRef = useRef(false); useField({ id, diff --git a/packages/react/src/number-field/root/NumberFieldRoot.tsx b/packages/react/src/number-field/root/NumberFieldRoot.tsx index e9424c239ae..3a82b4715de 100644 --- a/packages/react/src/number-field/root/NumberFieldRoot.tsx +++ b/packages/react/src/number-field/root/NumberFieldRoot.tsx @@ -9,6 +9,9 @@ import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; import { useForcedRerendering } from '@base-ui-components/utils/useForcedRerendering'; import { ownerDocument, ownerWindow } from '@base-ui-components/utils/owner'; import { isIOS } from '@base-ui-components/utils/detectBrowser'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { InputMode, NumberFieldRootContext } from './NumberFieldRootContext'; import { useFieldRootContext } from '../../field/root/FieldRootContext'; import type { FieldRoot } from '../../field/root/FieldRoot'; @@ -87,14 +90,14 @@ export const NumberFieldRoot = React.forwardRef(function NumberFieldRoot( const disabled = fieldDisabled || disabledProp; const name = fieldName ?? nameProp; - const [isScrubbing, setIsScrubbing] = React.useState(false); + const [isScrubbing, setIsScrubbing] = useState(false); const minWithDefault = min ?? Number.MIN_SAFE_INTEGER; const maxWithDefault = max ?? Number.MAX_SAFE_INTEGER; const minWithZeroDefault = min ?? 0; const formatStyle = format?.style; - const inputRef = React.useRef(null); + const inputRef = useRef(null); const id = useLabelableId({ id: idProp }); @@ -116,7 +119,7 @@ export const NumberFieldRoot = React.forwardRef(function NumberFieldRoot( const formatOptionsRef = useValueAsRef(format); - const hasPendingCommitRef = React.useRef(false); + const hasPendingCommitRef = useRef(false); const onValueCommitted = useStableCallback( (nextValue: number | null, eventDetails: NumberFieldRoot.CommitEventDetails) => { @@ -129,23 +132,23 @@ export const NumberFieldRoot = React.forwardRef(function NumberFieldRoot( const tickInterval = useInterval(); const intentionalTouchCheckTimeout = useTimeout(); - const isPressedRef = React.useRef(false); - const movesAfterTouchRef = React.useRef(0); - const allowInputSyncRef = React.useRef(true); - const lastChangedValueRef = React.useRef(null); - const unsubscribeFromGlobalContextMenuRef = React.useRef<() => void>(() => {}); + const isPressedRef = useRef(false); + const movesAfterTouchRef = useRef(0); + const allowInputSyncRef = useRef(true); + const lastChangedValueRef = useRef(null); + const unsubscribeFromGlobalContextMenuRef = useRef<() => void>(() => {}); // During SSR, the value is formatted on the server, whose locale may differ from the client's // locale. This causes a hydration mismatch, which we manually suppress. This is preferable to // rendering an empty input field and then updating it with the formatted value, as the user // can still see the value prior to hydration, even if it's not formatted correctly. - const [inputValue, setInputValue] = React.useState(() => { + const [inputValue, setInputValue] = useState(() => { if (valueProp !== undefined) { return getControlledInputValue(value, locale, format); } return formatNumber(value, locale, format); }); - const [inputMode, setInputMode] = React.useState('numeric'); + const [inputMode, setInputMode] = useState('numeric'); const getAllowedNonNumericKeys = useStableCallback(() => { const { decimal, group, currency, literal } = getNumberLocaleDetails(locale, format); @@ -376,12 +379,12 @@ export const NumberFieldRoot = React.forwardRef(function NumberFieldRoot( [minWithDefault, formatStyle], ); - React.useEffect(() => { + useEffect(() => { return () => stopAutoChange(); }, [stopAutoChange]); // The `onWheel` prop can't be prevented, so we need to use a global event listener. - React.useEffect( + useEffect( function registerElementWheelListener() { const element = inputRef.current; if (disabled || readOnly || !allowWheelScrub || !element) { diff --git a/packages/react/src/number-field/root/useNumberFieldButton.ts b/packages/react/src/number-field/root/useNumberFieldButton.ts index 8abcb342110..a3bd23050ed 100644 --- a/packages/react/src/number-field/root/useNumberFieldButton.ts +++ b/packages/react/src/number-field/root/useNumberFieldButton.ts @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useRef } from '@base-ui-components/utils/useRef'; import type { Timeout } from '@base-ui-components/utils/useTimeout'; import { DEFAULT_STEP, @@ -47,10 +48,10 @@ export function useNumberFieldButton(params: useNumberFieldButton.Parameters) { onValueCommitted, } = params; - const incrementDownCoordsRef = React.useRef({ x: 0, y: 0 }); - const isTouchingButtonRef = React.useRef(false); - const ignoreClickRef = React.useRef(false); - const pointerTypeRef = React.useRef<'mouse' | 'touch' | 'pen' | ''>(''); + const incrementDownCoordsRef = useRef({ x: 0, y: 0 }); + const isTouchingButtonRef = useRef(false); + const ignoreClickRef = useRef(false); + const pointerTypeRef = useRef<'mouse' | 'touch' | 'pen' | ''>(''); const isMin = value != null && value <= minWithDefault; const isMax = value != null && value >= maxWithDefault; diff --git a/packages/react/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.tsx b/packages/react/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.tsx index aa159037a8d..0b90f73252f 100644 --- a/packages/react/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.tsx +++ b/packages/react/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { isWebKit } from '@base-ui-components/utils/detectBrowser'; import { ownerDocument } from '@base-ui-components/utils/owner'; +import { useState } from '@base-ui-components/utils/useState'; import { useNumberFieldRootContext } from '../root/NumberFieldRootContext'; import type { BaseUIComponentProps } from '../../utils/types'; import type { NumberFieldRoot } from '../root/NumberFieldRoot'; @@ -29,7 +30,7 @@ export const NumberFieldScrubAreaCursor = React.forwardRef(function NumberFieldS const { isScrubbing, isTouchInput, isPointerLockDenied, scrubAreaCursorRef } = useNumberFieldScrubAreaContext(); - const [domElement, setDomElement] = React.useState(null); + const [domElement, setDomElement] = useState(null); const shouldRender = isScrubbing && !isWebKit && !isTouchInput && !isPointerLockDenied; diff --git a/packages/react/src/number-field/scrub-area/NumberFieldScrubArea.tsx b/packages/react/src/number-field/scrub-area/NumberFieldScrubArea.tsx index 2ad878c4875..3c551b658d0 100644 --- a/packages/react/src/number-field/scrub-area/NumberFieldScrubArea.tsx +++ b/packages/react/src/number-field/scrub-area/NumberFieldScrubArea.tsx @@ -5,6 +5,9 @@ import { ownerWindow, ownerDocument } from '@base-ui-components/utils/owner'; import { isWebKit } from '@base-ui-components/utils/detectBrowser'; import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import type { BaseUIComponentProps, HTMLProps } from '../../utils/types'; import { useNumberFieldRootContext } from '../root/NumberFieldRootContext'; import type { NumberFieldRoot } from '../root/NumberFieldRoot'; @@ -54,17 +57,17 @@ export const NumberFieldScrubArea = React.forwardRef(function NumberFieldScrubAr const latestValueRef = useValueAsRef(value); - const scrubAreaRef = React.useRef(null); + const scrubAreaRef = useRef(null); - const isScrubbingRef = React.useRef(false); - const scrubAreaCursorRef = React.useRef(null); - const virtualCursorCoords = React.useRef({ x: 0, y: 0 }); - const visualScaleRef = React.useRef(1); + const isScrubbingRef = useRef(false); + const scrubAreaCursorRef = useRef(null); + const virtualCursorCoords = useRef({ x: 0, y: 0 }); + const visualScaleRef = useRef(1); - const [isTouchInput, setIsTouchInput] = React.useState(false); - const [isPointerLockDenied, setIsPointerLockDenied] = React.useState(false); + const [isTouchInput, setIsTouchInput] = useState(false); + const [isPointerLockDenied, setIsPointerLockDenied] = useState(false); - React.useEffect(() => { + useEffect(() => { if (!isScrubbing || !scrubAreaCursorRef.current) { return undefined; } @@ -136,7 +139,7 @@ export const NumberFieldScrubArea = React.forwardRef(function NumberFieldScrubAr }, ); - React.useEffect( + useEffect( function registerGlobalScrubbingEventListeners() { // Only listen while actively scrubbing; avoids unrelated pointerup events committing. if (!inputRef.current || disabled || readOnly || !isScrubbing) { @@ -220,7 +223,7 @@ export const NumberFieldScrubArea = React.forwardRef(function NumberFieldScrubAr ); // Prevent scrolling using touch input when scrubbing. - React.useEffect( + useEffect( function registerScrubberTouchPreventListener() { const element = scrubAreaRef.current; if (!element || disabled || readOnly) { diff --git a/packages/react/src/popover/popup/PopoverPopup.tsx b/packages/react/src/popover/popup/PopoverPopup.tsx index b525962246a..3952997e1cb 100644 --- a/packages/react/src/popover/popup/PopoverPopup.tsx +++ b/packages/react/src/popover/popup/PopoverPopup.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import { InteractionType } from '@base-ui-components/utils/useEnhancedClickHandler'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { isHTMLElement } from '@floating-ui/utils/dom'; import { Dimensions, @@ -103,7 +104,7 @@ export const PopoverPopup = React.forwardRef(function PopoverPopup( [open, positioner.side, positioner.align, instantType, transitionStatus], ); - const setPopupElement = React.useCallback( + const setPopupElement = useCallback( (element: HTMLElement | null) => { store.set('popupElement', element); }, diff --git a/packages/react/src/popover/positioner/PopoverPositioner.tsx b/packages/react/src/popover/positioner/PopoverPositioner.tsx index 8a18f23c62c..d13d64322ce 100644 --- a/packages/react/src/popover/positioner/PopoverPositioner.tsx +++ b/packages/react/src/popover/positioner/PopoverPositioner.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import { inertValue } from '@base-ui-components/utils/inertValue'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { FloatingNode, useFloatingNodeId } from '../../floating-ui-react'; import { usePopoverRootContext } from '../root/PopoverRootContext'; import { PopoverPositionerContext } from './PopoverPositionerContext'; @@ -60,7 +62,7 @@ export const PopoverPositioner = React.forwardRef(function PopoverPositioner( const instantType = store.useState('instantType'); const transitionStatus = store.useState('transitionStatus'); - const prevTriggerElementRef = React.useRef(null); + const prevTriggerElementRef = useRef(null); const runOnceAnimationsFinish = useAnimationsFinished(positionerElement, false, false); @@ -151,7 +153,7 @@ export const PopoverPositioner = React.forwardRef(function PopoverPositioner( [open, positioner.side, positioner.align, positioner.anchorHidden, instantType], ); - const setPositionerElement = React.useCallback( + const setPositionerElement = useCallback( (element: HTMLElement | null) => { store.set('positionerElement', element); }, diff --git a/packages/react/src/popover/root/PopoverRoot.tsx b/packages/react/src/popover/root/PopoverRoot.tsx index b81520ceeaf..f57a56d349b 100644 --- a/packages/react/src/popover/root/PopoverRoot.tsx +++ b/packages/react/src/popover/root/PopoverRoot.tsx @@ -1,6 +1,8 @@ 'use client'; import * as React from 'react'; import { useScrollLock } from '@base-ui-components/utils/useScrollLock'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { useDismiss, useInteractions, @@ -71,13 +73,13 @@ function PopoverRootComponent({ props }: { props: PopoverRoot.Props { + useEffect(() => { if (!open) { store.context.stickIfOpenTimeout.clear(); } }, [store, open]); - const createPopoverEventDetails = React.useCallback( + const createPopoverEventDetails = useCallback( (reason: PopoverRoot.ChangeEventReason) => { const details: PopoverRoot.ChangeEventDetails = createChangeEventDetails( @@ -92,7 +94,7 @@ function PopoverRootComponent({ props }: { props: PopoverRoot.Props { + const handleImperativeClose = useCallback(() => { store.setOpen(false, createPopoverEventDetails(REASONS.imperativeAction)); }, [store, createPopoverEventDetails]); diff --git a/packages/react/src/popover/trigger/PopoverTrigger.tsx b/packages/react/src/popover/trigger/PopoverTrigger.tsx index 3a8520659e8..7af34b930e1 100644 --- a/packages/react/src/popover/trigger/PopoverTrigger.tsx +++ b/packages/react/src/popover/trigger/PopoverTrigger.tsx @@ -3,6 +3,8 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { type FocusableElement } from 'tabbable'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { usePopoverRootContext } from '../root/PopoverRootContext'; import { useButton } from '../../use-button/useButton'; import type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types'; @@ -71,7 +73,7 @@ export const PopoverTrigger = React.forwardRef(function PopoverTrigger( const floatingContext = store.useState('floatingRootContext'); const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId); - const [triggerElement, setTriggerElement] = React.useState(null); + const [triggerElement, setTriggerElement] = useState(null); const { registerTrigger, isMountedByThisTrigger } = useTriggerDataForwarding( thisTriggerId, @@ -151,7 +153,7 @@ export const PopoverTrigger = React.forwardRef(function PopoverTrigger( stateAttributesMapping, }); - const preFocusGuardRef = React.useRef(null); + const preFocusGuardRef = useRef(null); const handlePreFocusGuardFocus = useStableCallback((event: React.FocusEvent) => { ReactDOM.flushSync(() => { diff --git a/packages/react/src/popover/viewport/PopoverViewport.tsx b/packages/react/src/popover/viewport/PopoverViewport.tsx index e0f9a16edf6..19dfc1018d3 100644 --- a/packages/react/src/popover/viewport/PopoverViewport.tsx +++ b/packages/react/src/popover/viewport/PopoverViewport.tsx @@ -4,6 +4,9 @@ import { useAnimationFrame } from '@base-ui-components/utils/useAnimationFrame'; import { usePreviousValue } from '@base-ui-components/utils/usePreviousValue'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { usePopoverRootContext } from '../root/PopoverRootContext'; import { BaseUIComponentProps } from '../../utils/types'; import { useAnimationsFinished } from '../../utils/useAnimationsFinished'; @@ -42,23 +45,23 @@ export const PopoverViewport = React.forwardRef(function PopoverViewport( const previousActiveTrigger = usePreviousValue(open ? activeTrigger : null); - const capturedNodeRef = React.useRef(null); - const [previousContentNode, setPreviousContentNode] = React.useState(null); + const capturedNodeRef = useRef(null); + const [previousContentNode, setPreviousContentNode] = useState(null); - const [newTriggerOffset, setNewTriggerOffset] = React.useState(null); + const [newTriggerOffset, setNewTriggerOffset] = useState(null); - const currentContainerRef = React.useRef(null); - const previousContainerRef = React.useRef(null); + const currentContainerRef = useRef(null); + const previousContainerRef = useRef(null); const onAnimationsFinished = useAnimationsFinished(currentContainerRef, true, false); const cleanupTimeout = useAnimationFrame(); - const [previousContentDimensions, setPreviousContentDimensions] = React.useState<{ + const [previousContentDimensions, setPreviousContentDimensions] = useState<{ width: number; height: number; } | null>(null); - const [showStartingStyleAttribute, setShowStartingStyleAttribute] = React.useState(false); + const [showStartingStyleAttribute, setShowStartingStyleAttribute] = useState(false); // Capture a clone of the current content DOM subtree when not transitioning. // We can't store previous React nodes as they may be stateful; instead we capture DOM clones for visual continuity. @@ -105,7 +108,7 @@ export const PopoverViewport = React.forwardRef(function PopoverViewport( } }); - React.useEffect(() => { + useEffect(() => { floatingContext.context.events.on('measure-layout', handleMeasureLayout); floatingContext.context.events.on('measure-layout-complete', handleMeasureLayoutComplete); @@ -115,7 +118,7 @@ export const PopoverViewport = React.forwardRef(function PopoverViewport( }; }, [floatingContext, handleMeasureLayout, handleMeasureLayoutComplete]); - const lastHandledTriggerRef = React.useRef(null); + const lastHandledTriggerRef = useRef(null); useIsoLayoutEffect(() => { // When a trigger changes, set the captured children HTML to state, diff --git a/packages/react/src/preview-card/root/PreviewCardRoot.tsx b/packages/react/src/preview-card/root/PreviewCardRoot.tsx index a3fb9452e21..1562ba9aaa8 100644 --- a/packages/react/src/preview-card/root/PreviewCardRoot.tsx +++ b/packages/react/src/preview-card/root/PreviewCardRoot.tsx @@ -3,6 +3,8 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { useControlled } from '@base-ui-components/utils/useControlled'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { safePolygon, useDismiss, @@ -33,19 +35,19 @@ export function PreviewCardRoot(props: PreviewCardRoot.Props) { actionsRef, } = props; - const delayRef = React.useRef(OPEN_DELAY); - const closeDelayRef = React.useRef(CLOSE_DELAY); + const delayRef = useRef(OPEN_DELAY); + const closeDelayRef = useRef(CLOSE_DELAY); const writeDelayRefs = useStableCallback((config: PreviewCardTriggerDelayConfig) => { delayRef.current = config.delay ?? OPEN_DELAY; closeDelayRef.current = config.closeDelay ?? CLOSE_DELAY; }); - const [triggerElement, setTriggerElement] = React.useState(null); - const [positionerElement, setPositionerElement] = React.useState(null); - const [instantTypeState, setInstantTypeState] = React.useState<'dismiss' | 'focus'>(); + const [triggerElement, setTriggerElement] = useState(null); + const [positionerElement, setPositionerElement] = useState(null); + const [instantTypeState, setInstantTypeState] = useState<'dismiss' | 'focus'>(); - const popupRef = React.useRef(null); + const popupRef = useRef(null); const [open, setOpenUnwrapped] = useControlled({ controlled: externalOpen, diff --git a/packages/react/src/progress/indicator/ProgressIndicator.tsx b/packages/react/src/progress/indicator/ProgressIndicator.tsx index 7a9ae938146..d7c3e5d7ee1 100644 --- a/packages/react/src/progress/indicator/ProgressIndicator.tsx +++ b/packages/react/src/progress/indicator/ProgressIndicator.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { useRenderElement } from '../../utils/useRenderElement'; import { valueToPercent } from '../../utils/valueToPercent'; import type { ProgressRoot } from '../root/ProgressRoot'; @@ -24,7 +25,7 @@ export const ProgressIndicator = React.forwardRef(function ProgressIndicator( const percentageValue = Number.isFinite(value) && value !== null ? valueToPercent(value, min, max) : null; - const getStyles = React.useCallback(() => { + const getStyles = useCallback(() => { if (percentageValue == null) { return {}; } diff --git a/packages/react/src/progress/root/ProgressRoot.tsx b/packages/react/src/progress/root/ProgressRoot.tsx index 4ce2ae90ba1..6cc39a3862f 100644 --- a/packages/react/src/progress/root/ProgressRoot.tsx +++ b/packages/react/src/progress/root/ProgressRoot.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; +import { useState } from '@base-ui-components/utils/useState'; import { formatNumber } from '../../utils/formatNumber'; import { useRenderElement } from '../../utils/useRenderElement'; import { ProgressRootContext } from './ProgressRootContext'; @@ -53,7 +54,7 @@ export const ProgressRoot = React.forwardRef(function ProgressRoot( ...elementProps } = componentProps; - const [labelId, setLabelId] = React.useState(); + const [labelId, setLabelId] = useState(); const formatOptionsRef = useValueAsRef(format); diff --git a/packages/react/src/radio-group/RadioGroup.tsx b/packages/react/src/radio-group/RadioGroup.tsx index af401be0c51..66b11ab52be 100644 --- a/packages/react/src/radio-group/RadioGroup.tsx +++ b/packages/react/src/radio-group/RadioGroup.tsx @@ -4,6 +4,8 @@ import { useControlled } from '@base-ui-components/utils/useControlled'; import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { visuallyHidden } from '@base-ui-components/utils/visuallyHidden'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { NOOP } from '../utils/noop'; import type { BaseUIComponentProps, HTMLProps } from '../utils/types'; import { useBaseUiId } from '../utils/useBaseUiId'; @@ -92,7 +94,7 @@ export const RadioGroup = React.forwardRef(function RadioGroup( }, ); - const controlRef = React.useRef(null); + const controlRef = useRef(null); const registerControlRef = useStableCallback((element: HTMLElement | null) => { if (controlRef.current == null && element != null) { controlRef.current = element; @@ -121,7 +123,7 @@ export const RadioGroup = React.forwardRef(function RadioGroup( } }); - const [touched, setTouched] = React.useState(false); + const [touched, setTouched] = useState(false); const onBlur = useStableCallback((event) => { if (!contains(event.currentTarget, event.relatedTarget)) { diff --git a/packages/react/src/radio/indicator/RadioIndicator.tsx b/packages/react/src/radio/indicator/RadioIndicator.tsx index 3bf89c4fec6..205cb401dc9 100644 --- a/packages/react/src/radio/indicator/RadioIndicator.tsx +++ b/packages/react/src/radio/indicator/RadioIndicator.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useRef } from '@base-ui-components/utils/useRef'; import type { BaseUIComponentProps } from '../../utils/types'; import { useRenderElement } from '../../utils/useRenderElement'; import { useRadioRootContext } from '../root/RadioRootContext'; @@ -33,7 +34,7 @@ export const RadioIndicator = React.forwardRef(function RadioIndicator( [rootState, transitionStatus], ); - const indicatorRef = React.useRef(null); + const indicatorRef = useRef(null); const shouldRender = keepMounted || rendered; diff --git a/packages/react/src/radio/root/RadioRoot.tsx b/packages/react/src/radio/root/RadioRoot.tsx index 1cd2314faf4..d9ff6985714 100644 --- a/packages/react/src/radio/root/RadioRoot.tsx +++ b/packages/react/src/radio/root/RadioRoot.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { visuallyHidden } from '@base-ui-components/utils/visuallyHidden'; +import { useRef } from '@base-ui-components/utils/useRef'; import type { BaseUIComponentProps, NonNativeButtonProps } from '../../utils/types'; import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails'; import { REASONS } from '../../utils/reasons'; @@ -74,8 +75,8 @@ export const RadioRoot = React.forwardRef(function RadioRoot( const checked = checkedValue === value; - const radioRef = React.useRef(null); - const inputRef = React.useRef(null); + const radioRef = useRef(null); + const inputRef = useRef(null); const mergedInputRef = useMergedRefs(inputRefProp, inputRef); useIsoLayoutEffect(() => { diff --git a/packages/react/src/scroll-area/content/ScrollAreaContent.tsx b/packages/react/src/scroll-area/content/ScrollAreaContent.tsx index cc0e34499d7..e22681caab6 100644 --- a/packages/react/src/scroll-area/content/ScrollAreaContent.tsx +++ b/packages/react/src/scroll-area/content/ScrollAreaContent.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import type { BaseUIComponentProps } from '../../utils/types'; import { useScrollAreaViewportContext } from '../viewport/ScrollAreaViewportContext'; import { useRenderElement } from '../../utils/useRenderElement'; @@ -20,7 +21,7 @@ export const ScrollAreaContent = React.forwardRef(function ScrollAreaContent( ) { const { render, className, ...elementProps } = componentProps; - const contentWrapperRef = React.useRef(null); + const contentWrapperRef = useRef(null); const { computeThumbPosition } = useScrollAreaViewportContext(); const { viewportState } = useScrollAreaRootContext(); diff --git a/packages/react/src/scroll-area/root/ScrollAreaRoot.tsx b/packages/react/src/scroll-area/root/ScrollAreaRoot.tsx index 1452b1b98cc..57ef8f3b3ea 100644 --- a/packages/react/src/scroll-area/root/ScrollAreaRoot.tsx +++ b/packages/react/src/scroll-area/root/ScrollAreaRoot.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import type { BaseUIComponentProps, HTMLProps } from '../../utils/types'; import { ScrollAreaRootContext } from './ScrollAreaRootContext'; import { useRenderElement } from '../../utils/useRenderElement'; @@ -47,35 +49,35 @@ export const ScrollAreaRoot = React.forwardRef(function ScrollAreaRoot( ...elementProps } = componentProps; - const [hovering, setHovering] = React.useState(false); - const [scrollingX, setScrollingX] = React.useState(false); - const [scrollingY, setScrollingY] = React.useState(false); - const [cornerSize, setCornerSize] = React.useState(DEFAULT_SIZE); - const [thumbSize, setThumbSize] = React.useState(DEFAULT_SIZE); - const [touchModality, setTouchModality] = React.useState(false); - const [overflowEdges, setOverflowEdges] = React.useState(DEFAULT_OVERFLOW_EDGES); + const [hovering, setHovering] = useState(false); + const [scrollingX, setScrollingX] = useState(false); + const [scrollingY, setScrollingY] = useState(false); + const [cornerSize, setCornerSize] = useState(DEFAULT_SIZE); + const [thumbSize, setThumbSize] = useState(DEFAULT_SIZE); + const [touchModality, setTouchModality] = useState(false); + const [overflowEdges, setOverflowEdges] = useState(DEFAULT_OVERFLOW_EDGES); const rootId = useBaseUiId(); - const rootRef = React.useRef(null); - const viewportRef = React.useRef(null); - const scrollbarYRef = React.useRef(null); - const scrollbarXRef = React.useRef(null); - const thumbYRef = React.useRef(null); - const thumbXRef = React.useRef(null); - const cornerRef = React.useRef(null); - - const thumbDraggingRef = React.useRef(false); - const startYRef = React.useRef(0); - const startXRef = React.useRef(0); - const startScrollTopRef = React.useRef(0); - const startScrollLeftRef = React.useRef(0); - const currentOrientationRef = React.useRef<'vertical' | 'horizontal'>('vertical'); + const rootRef = useRef(null); + const viewportRef = useRef(null); + const scrollbarYRef = useRef(null); + const scrollbarXRef = useRef(null); + const thumbYRef = useRef(null); + const thumbXRef = useRef(null); + const cornerRef = useRef(null); + + const thumbDraggingRef = useRef(false); + const startYRef = useRef(0); + const startXRef = useRef(0); + const startScrollTopRef = useRef(0); + const startScrollLeftRef = useRef(0); + const currentOrientationRef = useRef<'vertical' | 'horizontal'>('vertical'); const scrollYTimeout = useTimeout(); const scrollXTimeout = useTimeout(); - const scrollPositionRef = React.useRef({ x: 0, y: 0 }); + const scrollPositionRef = useRef({ x: 0, y: 0 }); - const [hiddenState, setHiddenState] = React.useState({ + const [hiddenState, setHiddenState] = useState({ scrollbarYHidden: false, scrollbarXHidden: false, cornerHidden: false, diff --git a/packages/react/src/scroll-area/scrollbar/ScrollAreaScrollbar.tsx b/packages/react/src/scroll-area/scrollbar/ScrollAreaScrollbar.tsx index 306d7373437..fabc5fe0ae7 100644 --- a/packages/react/src/scroll-area/scrollbar/ScrollAreaScrollbar.tsx +++ b/packages/react/src/scroll-area/scrollbar/ScrollAreaScrollbar.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useEffect } from '@base-ui-components/utils/useEffect'; import type { BaseUIComponentProps, HTMLProps } from '../../utils/types'; import { useScrollAreaRootContext } from '../root/ScrollAreaRootContext'; import { ScrollAreaScrollbarContext } from './ScrollAreaScrollbarContext'; @@ -67,7 +68,7 @@ export const ScrollAreaScrollbar = React.forwardRef(function ScrollAreaScrollbar const direction = useDirection(); - React.useEffect(() => { + useEffect(() => { const viewportEl = viewportRef.current; const scrollbarEl = orientation === 'vertical' ? scrollbarYRef.current : scrollbarXRef.current; diff --git a/packages/react/src/scroll-area/viewport/ScrollAreaViewport.tsx b/packages/react/src/scroll-area/viewport/ScrollAreaViewport.tsx index aabbb293c7f..acf2cd24e10 100644 --- a/packages/react/src/scroll-area/viewport/ScrollAreaViewport.tsx +++ b/packages/react/src/scroll-area/viewport/ScrollAreaViewport.tsx @@ -5,6 +5,8 @@ import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { isWebKit } from '@base-ui-components/utils/detectBrowser'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import type { BaseUIComponentProps } from '../../utils/types'; import { useScrollAreaRootContext } from '../root/ScrollAreaRootContext'; import { ScrollAreaViewportContext } from './ScrollAreaViewportContext'; @@ -97,7 +99,7 @@ export const ScrollAreaViewport = React.forwardRef(function ScrollAreaViewport( const direction = useDirection(); - const programmaticScrollRef = React.useRef(true); + const programmaticScrollRef = useRef(true); const scrollEndTimeout = useTimeout(); const waitForAnimationsTimeout = useTimeout(); @@ -299,7 +301,7 @@ export const ScrollAreaViewport = React.forwardRef(function ScrollAreaViewport( } }, [viewportRef, setHovering]); - React.useEffect(() => { + useEffect(() => { const viewport = viewportRef.current; if (typeof ResizeObserver === 'undefined' || !viewport) { return undefined; diff --git a/packages/react/src/select/group/SelectGroup.tsx b/packages/react/src/select/group/SelectGroup.tsx index 4af9af38374..d15cb92ef4f 100644 --- a/packages/react/src/select/group/SelectGroup.tsx +++ b/packages/react/src/select/group/SelectGroup.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useState } from '@base-ui-components/utils/useState'; import type { BaseUIComponentProps } from '../../utils/types'; import { SelectGroupContext } from './SelectGroupContext'; import { useRenderElement } from '../../utils/useRenderElement'; @@ -16,7 +17,7 @@ export const SelectGroup = React.forwardRef(function SelectGroup( ) { const { className, render, ...elementProps } = componentProps; - const [labelId, setLabelId] = React.useState(); + const [labelId, setLabelId] = useState(); const contextValue: SelectGroupContext = React.useMemo( () => ({ diff --git a/packages/react/src/select/item-indicator/SelectItemIndicator.tsx b/packages/react/src/select/item-indicator/SelectItemIndicator.tsx index 0c2e6543249..ee3494ff615 100644 --- a/packages/react/src/select/item-indicator/SelectItemIndicator.tsx +++ b/packages/react/src/select/item-indicator/SelectItemIndicator.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useRef } from '@base-ui-components/utils/useRef'; import type { BaseUIComponentProps } from '../../utils/types'; import { useSelectItemContext } from '../item/SelectItemContext'; import { type TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus'; @@ -42,7 +43,7 @@ const Inner = React.memo( const { selected } = useSelectItemContext(); - const indicatorRef = React.useRef(null); + const indicatorRef = useRef(null); const { transitionStatus, setMounted } = useTransitionStatus(selected); diff --git a/packages/react/src/select/item-text/SelectItemText.tsx b/packages/react/src/select/item-text/SelectItemText.tsx index d49759e6dc7..459465f72b3 100644 --- a/packages/react/src/select/item-text/SelectItemText.tsx +++ b/packages/react/src/select/item-text/SelectItemText.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import type { BaseUIComponentProps } from '../../utils/types'; import { useSelectRootContext } from '../root/SelectRootContext'; import { useSelectItemContext } from '../item/SelectItemContext'; @@ -21,7 +22,7 @@ export const SelectItemText = React.memo( const { className, render, ...elementProps } = componentProps; - const localRef = React.useCallback( + const localRef = useCallback( (node: HTMLElement | null) => { if (!node || !hasRegistered) { return; diff --git a/packages/react/src/select/item/SelectItem.tsx b/packages/react/src/select/item/SelectItem.tsx index 5703cf4cb0a..21ff9c9b882 100644 --- a/packages/react/src/select/item/SelectItem.tsx +++ b/packages/react/src/select/item/SelectItem.tsx @@ -5,6 +5,7 @@ import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; import { isMouseWithinBounds } from '@base-ui-components/utils/isMouseWithinBounds'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; import { useStore } from '@base-ui-components/utils/store'; +import { useRef } from '@base-ui-components/utils/useRef'; import { useSelectRootContext } from '../root/SelectRootContext'; import { useCompositeListItem, @@ -40,7 +41,7 @@ export const SelectItem = React.memo( ...elementProps } = componentProps; - const textRef = React.useRef(null); + const textRef = useRef(null); const listItem = useCompositeListItem({ label, textRef, @@ -69,7 +70,7 @@ export const SelectItem = React.memo( const index = listItem.index; const hasRegistered = index !== -1; - const itemRef = React.useRef(null); + const itemRef = useRef(null); const indexRef = useValueAsRef(index); useIsoLayoutEffect(() => { @@ -118,9 +119,9 @@ export const SelectItem = React.memo( rootProps.onFocus = undefined; rootProps.id = undefined; - const lastKeyRef = React.useRef(null); - const pointerTypeRef = React.useRef<'mouse' | 'touch' | 'pen'>('mouse'); - const didPointerDownRef = React.useRef(false); + const lastKeyRef = useRef(null); + const pointerTypeRef = useRef<'mouse' | 'touch' | 'pen'>('mouse'); + const didPointerDownRef = useRef(false); const { getButtonProps, buttonRef } = useButton({ disabled, diff --git a/packages/react/src/select/popup/SelectPopup.tsx b/packages/react/src/select/popup/SelectPopup.tsx index b95f03a8f45..951e6889c03 100644 --- a/packages/react/src/select/popup/SelectPopup.tsx +++ b/packages/react/src/select/popup/SelectPopup.tsx @@ -9,6 +9,8 @@ import { isMouseWithinBounds } from '@base-ui-components/utils/isMouseWithinBoun import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { useStore } from '@base-ui-components/utils/store'; import { useAnimationFrame } from '@base-ui-components/utils/useAnimationFrame'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { FloatingFocusManager } from '../../floating-ui-react'; import type { BaseUIComponentProps, HTMLProps } from '../../utils/types'; import { useSelectFloatingContext, useSelectRootContext } from '../root/SelectRootContext'; @@ -80,11 +82,11 @@ export const SelectPopup = React.forwardRef(function SelectPopup( const positionerElement = useStore(store, selectors.positionerElement); const listElement = useStore(store, selectors.listElement); - const initialHeightRef = React.useRef(0); - const reachedMaxHeightRef = React.useRef(false); - const maxHeightRef = React.useRef(0); - const initialPlacedRef = React.useRef(false); - const originalPositionerStylesRef = React.useRef({}); + const initialHeightRef = useRef(0); + const reachedMaxHeightRef = useRef(false); + const maxHeightRef = useRef(0); + const initialPlacedRef = useRef(false); + const originalPositionerStylesRef = useRef({}); const scrollArrowFrame = useAnimationFrame(); @@ -355,7 +357,7 @@ export const SelectPopup = React.forwardRef(function SelectPopup( listElement, ]); - React.useEffect(() => { + useEffect(() => { if (!alignItemWithTriggerActive || !positionerElement || !mounted) { return undefined; } diff --git a/packages/react/src/select/positioner/SelectPositioner.tsx b/packages/react/src/select/positioner/SelectPositioner.tsx index c639f7b715b..9753c45cc28 100644 --- a/packages/react/src/select/positioner/SelectPositioner.tsx +++ b/packages/react/src/select/positioner/SelectPositioner.tsx @@ -5,6 +5,8 @@ import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect import { useScrollLock } from '@base-ui-components/utils/useScrollLock'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useStore } from '@base-ui-components/utils/store'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { useSelectRootContext, useSelectFloatingContext } from '../root/SelectRootContext'; import { CompositeList } from '../../composite/list/CompositeList'; import type { BaseUIComponentProps } from '../../utils/types'; @@ -73,11 +75,11 @@ export const SelectPositioner = React.forwardRef(function SelectPositioner( const triggerElement = useStore(store, selectors.triggerElement); const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue); - const scrollUpArrowRef = React.useRef(null); - const scrollDownArrowRef = React.useRef(null); + const scrollUpArrowRef = useRef(null); + const scrollDownArrowRef = useRef(null); const [controlledAlignItemWithTrigger, setControlledAlignItemWithTrigger] = - React.useState(alignItemWithTrigger); + useState(alignItemWithTrigger); const alignItemWithTriggerActive = mounted && controlledAlignItemWithTrigger && !touchModality; if (!mounted && controlledAlignItemWithTrigger !== alignItemWithTrigger) { @@ -158,7 +160,7 @@ export const SelectPositioner = React.forwardRef(function SelectPositioner( props: [defaultProps, elementProps], }); - const prevMapSizeRef = React.useRef(0); + const prevMapSizeRef = useRef(0); const onMapChange = useStableCallback((map: Map) => { if (map.size === 0 && prevMapSizeRef.current === 0) { diff --git a/packages/react/src/select/root/SelectRoot.spec.tsx b/packages/react/src/select/root/SelectRoot.spec.tsx index 40ae31868d4..bfcf7d75fcc 100644 --- a/packages/react/src/select/root/SelectRoot.spec.tsx +++ b/packages/react/src/select/root/SelectRoot.spec.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useState } from '@base-ui-components/utils/useState'; import { expectType } from '#test-utils'; import { Select } from '@base-ui-components/react/select'; import { mergeProps } from '../../merge-props'; @@ -159,7 +160,7 @@ const objectValueItems: Array<{ value: Obj; label: string }> = [ />; function App() { - const [multiple, setMultiple] = React.useState(false); + const [multiple, setMultiple] = useState(false); return ( ; function App2() { - const [value, setValue] = React.useState('a'); + const [value, setValue] = useState('a'); return ( ('a'); + const [value, setValue] = useState('a'); return ( ( state: 'open', }); - const listRef = React.useRef>([]); - const labelsRef = React.useRef>([]); - const popupRef = React.useRef(null); - const scrollHandlerRef = React.useRef<((el: HTMLDivElement) => void) | null>(null); - const scrollArrowsMountedCountRef = React.useRef(0); - const valueRef = React.useRef(null); - const valuesRef = React.useRef>([]); - const typingRef = React.useRef(false); - const keyboardActiveRef = React.useRef(false); - const selectedItemTextRef = React.useRef(null); - const selectionRef = React.useRef({ + const listRef = useRef>([]); + const labelsRef = useRef>([]); + const popupRef = useRef(null); + const scrollHandlerRef = useRef<((el: HTMLDivElement) => void) | null>(null); + const scrollArrowsMountedCountRef = useRef(0); + const valueRef = useRef(null); + const valuesRef = useRef>([]); + const typingRef = useRef(false); + const keyboardActiveRef = useRef(false); + const selectedItemTextRef = useRef(null); + const selectionRef = useRef({ allowSelectedMouseUp: false, allowUnselectedMouseUp: false, }); - const alignItemWithTriggerActiveRef = React.useRef(false); + const alignItemWithTriggerActiveRef = useRef(false); const { mounted, setMounted, transitionStatus } = useTransitionStatus(open); @@ -170,7 +171,7 @@ export function SelectRoot( getValue: () => value, }); - const initialValueRef = React.useRef(value); + const initialValueRef = useRef(value); useIsoLayoutEffect(() => { // Ensure the values and labels are registered for programmatic value changes. if (value !== initialValueRef.current) { diff --git a/packages/react/src/select/trigger/SelectTrigger.tsx b/packages/react/src/select/trigger/SelectTrigger.tsx index 315b74dac47..907086984c0 100644 --- a/packages/react/src/select/trigger/SelectTrigger.tsx +++ b/packages/react/src/select/trigger/SelectTrigger.tsx @@ -6,6 +6,8 @@ import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'; import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; import { useStore } from '@base-ui-components/utils/store'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { useSelectRootContext } from '../root/SelectRootContext'; import { BaseUIComponentProps, HTMLProps, NativeButtonProps } from '../../utils/types'; import { useFieldRootContext } from '../../field/root/FieldRootContext'; @@ -79,7 +81,7 @@ export const SelectTrigger = React.forwardRef(function SelectTrigger( const positionerRef = useValueAsRef(positionerElement); - const triggerRef = React.useRef(null); + const triggerRef = useRef(null); const timeoutFocus = useTimeout(); const timeoutMouseDown = useTimeout(); @@ -102,7 +104,7 @@ export const SelectTrigger = React.forwardRef(function SelectTrigger( const timeout1 = useTimeout(); const timeout2 = useTimeout(); - React.useEffect(() => { + useEffect(() => { if (open) { // mousedown -> move to unselected item -> mouseup should not select within 200ms. timeout2.start(200, () => { diff --git a/packages/react/src/slider/control/SliderControl.tsx b/packages/react/src/slider/control/SliderControl.tsx index 4edbfc955f0..06c29818bd9 100644 --- a/packages/react/src/slider/control/SliderControl.tsx +++ b/packages/react/src/slider/control/SliderControl.tsx @@ -5,6 +5,8 @@ import { ownerDocument } from '@base-ui-components/utils/owner'; import { useAnimationFrame } from '@base-ui-components/utils/useAnimationFrame'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { activeElement, contains } from '../../floating-ui-react/utils'; import type { Coords } from '../../floating-ui-react/types'; import { clamp } from '../../utils/clamp'; @@ -119,8 +121,8 @@ export const SliderControl = React.forwardRef(function SliderControl( const range = values.length > 1; const vertical = orientation === 'vertical'; - const controlRef = React.useRef(null); - const stylesRef = React.useRef(null); + const controlRef = useRef(null); + const stylesRef = useRef(null); const setStylesRef = useStableCallback((element: HTMLElement | null) => { if (element && stylesRef.current == null) { if (stylesRef.current == null) { @@ -130,12 +132,12 @@ export const SliderControl = React.forwardRef(function SliderControl( }); // A number that uniquely identifies the current finger in the touch session. - const touchIdRef = React.useRef(null); + const touchIdRef = useRef(null); // The number of touch/pointermove events that have fired. - const moveCountRef = React.useRef(0); + const moveCountRef = useRef(0); // The offset amount to each side of the control for inset sliders. // This value should be equal to the radius or half the width/height of the thumb. - const insetThumbOffsetRef = React.useRef(0); + const insetThumbOffsetRef = useRef(0); const latestValuesRef = useValueAsRef(values); const updatePressedThumb = useStableCallback((nextIndex: number) => { @@ -403,7 +405,7 @@ export const SliderControl = React.forwardRef(function SliderControl( const focusFrame = useAnimationFrame(); - React.useEffect(() => { + useEffect(() => { const control = controlRef.current; if (!control) { return () => stopListening(); @@ -421,7 +423,7 @@ export const SliderControl = React.forwardRef(function SliderControl( }; }, [stopListening, handleTouchStart, controlRef, focusFrame]); - React.useEffect(() => { + useEffect(() => { if (disabled) { stopListening(); } diff --git a/packages/react/src/slider/indicator/SliderIndicator.tsx b/packages/react/src/slider/indicator/SliderIndicator.tsx index 15d1c36d6c5..c2ce17050ea 100644 --- a/packages/react/src/slider/indicator/SliderIndicator.tsx +++ b/packages/react/src/slider/indicator/SliderIndicator.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import { useOnMount } from '@base-ui-components/utils/useOnMount'; +import { useState } from '@base-ui-components/utils/useState'; import type { BaseUIComponentProps } from '../../utils/types'; import { valueToPercent } from '../../utils/valueToPercent'; import { useRenderElement } from '../../utils/useRenderElement'; @@ -91,7 +92,7 @@ export const SliderIndicator = React.forwardRef(function SliderIndicator( const { indicatorPosition, inset, max, min, orientation, renderBeforeHydration, state, values } = useSliderRootContext(); - const [isMounted, setIsMounted] = React.useState(false); + const [isMounted, setIsMounted] = useState(false); useOnMount(() => setIsMounted(true)); const vertical = orientation === 'vertical'; diff --git a/packages/react/src/slider/root/SliderRoot.tsx b/packages/react/src/slider/root/SliderRoot.tsx index 0dee88a7145..892d148edb3 100644 --- a/packages/react/src/slider/root/SliderRoot.tsx +++ b/packages/react/src/slider/root/SliderRoot.tsx @@ -6,6 +6,8 @@ import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { warn } from '@base-ui-components/utils/warn'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import type { BaseUIComponentProps, Orientation } from '../../utils/types'; import { createChangeEventDetails, @@ -124,34 +126,34 @@ export const SliderRoot = React.forwardRef(function SliderRoot< name: 'Slider', }); - const sliderRef = React.useRef(null); - const controlRef = React.useRef(null); - const thumbRefs = React.useRef<(HTMLElement | null)[]>([]); + const sliderRef = useRef(null); + const controlRef = useRef(null); + const thumbRefs = useRef<(HTMLElement | null)[]>([]); // The input element nested in the pressed thumb. - const pressedInputRef = React.useRef(null); + const pressedInputRef = useRef(null); // The px distance between the pointer and the center of a pressed thumb. - const pressedThumbCenterOffsetRef = React.useRef(null); + const pressedThumbCenterOffsetRef = useRef(null); // The index of the pressed thumb, or the closest thumb if the `Control` was pressed. // This is updated on pointerdown, which is sooner than the `active/activeIndex` // state which is updated later when the nested `input` receives focus. - const pressedThumbIndexRef = React.useRef(-1); + const pressedThumbIndexRef = useRef(-1); // The values when the current drag interaction started. - const pressedValuesRef = React.useRef(null); - const lastChangedValueRef = React.useRef(null); - const lastChangeReasonRef = React.useRef('none'); + const pressedValuesRef = useRef(null); + const lastChangedValueRef = useRef(null); + const lastChangeReasonRef = useRef('none'); const formatOptionsRef = useValueAsRef(format); // We can't use the :active browser pseudo-classes. // - The active state isn't triggered when clicking on the rail. // - The active state isn't transferred when inversing a range slider. - const [active, setActiveState] = React.useState(-1); - const [lastUsedThumbIndex, setLastUsedThumbIndex] = React.useState(-1); - const [dragging, setDragging] = React.useState(false); - const [thumbMap, setThumbMap] = React.useState( + const [active, setActiveState] = useState(-1); + const [lastUsedThumbIndex, setLastUsedThumbIndex] = useState(-1); + const [dragging, setDragging] = useState(false); + const [thumbMap, setThumbMap] = useState( () => new Map | null>(), ); - const [indicatorPosition, setIndicatorPosition] = React.useState<(number | undefined)[]>([ + const [indicatorPosition, setIndicatorPosition] = useState<(number | undefined)[]>([ undefined, undefined, ]); diff --git a/packages/react/src/slider/thumb/SliderThumb.tsx b/packages/react/src/slider/thumb/SliderThumb.tsx index 3ab35e78675..13dec1951a4 100644 --- a/packages/react/src/slider/thumb/SliderThumb.tsx +++ b/packages/react/src/slider/thumb/SliderThumb.tsx @@ -5,6 +5,9 @@ import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect import { useOnMount } from '@base-ui-components/utils/useOnMount'; import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'; import { visuallyHidden } from '@base-ui-components/utils/visuallyHidden'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; +import { useState } from '@base-ui-components/utils/useState'; import { BaseUIComponentProps } from '../../utils/types'; import { formatNumber } from '../../utils/formatNumber'; import { mergeProps } from '../../merge-props'; @@ -148,8 +151,8 @@ export const SliderThumb = React.forwardRef(function SliderThumb( const { setTouched, setFocused, validationMode } = useFieldRootContext(); - const thumbRef = React.useRef(null); - const inputRef = React.useRef(null); + const thumbRef = useRef(null); + const inputRef = useRef(null); const defaultInputId = useBaseUiId(); const labelableId = useLabelableId(); @@ -171,8 +174,8 @@ export const SliderThumb = React.forwardRef(function SliderThumb( const thumbValue = sliderValues[index]; const thumbValuePercent = valueToPercent(thumbValue, min, max); - const [isMounted, setIsMounted] = React.useState(false); - const [positionPercent, setPositionPercent] = React.useState(); + const [isMounted, setIsMounted] = useState(false); + const [positionPercent, setPositionPercent] = useState(); useOnMount(() => setIsMounted(true)); @@ -215,7 +218,7 @@ export const SliderThumb = React.forwardRef(function SliderThumb( } }, [getInsetPosition, inset, thumbValuePercent]); - const getThumbStyle = React.useCallback(() => { + const getThumbStyle = useCallback(() => { const startEdge = vertical ? 'bottom' : 'insetInlineStart'; const crossOffsetProperty = vertical ? 'left' : 'top'; diff --git a/packages/react/src/switch/root/SwitchRoot.tsx b/packages/react/src/switch/root/SwitchRoot.tsx index ca7b12a6d84..f005bd44476 100644 --- a/packages/react/src/switch/root/SwitchRoot.tsx +++ b/packages/react/src/switch/root/SwitchRoot.tsx @@ -5,6 +5,7 @@ import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { visuallyHidden } from '@base-ui-components/utils/visuallyHidden'; +import { useRef } from '@base-ui-components/utils/useRef'; import { useRenderElement } from '../../utils/useRenderElement'; import type { BaseUIComponentProps, NonNativeButtonProps } from '../../utils/types'; import { mergeProps } from '../../merge-props'; @@ -70,10 +71,10 @@ export const SwitchRoot = React.forwardRef(function SwitchRoot( const onCheckedChange = useStableCallback(onCheckedChangeProp); - const inputRef = React.useRef(null); + const inputRef = useRef(null); const handleInputRef = useMergedRefs(inputRef, externalInputRef, validation.inputRef); - const switchRef = React.useRef(null); + const switchRef = useRef(null); const id = useBaseUiId(); diff --git a/packages/react/src/tabs/indicator/TabsIndicator.tsx b/packages/react/src/tabs/indicator/TabsIndicator.tsx index d33f841a6ca..d8cfa45fd4e 100644 --- a/packages/react/src/tabs/indicator/TabsIndicator.tsx +++ b/packages/react/src/tabs/indicator/TabsIndicator.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import { useForcedRerendering } from '@base-ui-components/utils/useForcedRerendering'; import { useOnMount } from '@base-ui-components/utils/useOnMount'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useState } from '@base-ui-components/utils/useState'; import { useRenderElement } from '../../utils/useRenderElement'; import type { BaseUIComponentProps } from '../../utils/types'; import { useDirection } from '../../direction-provider/DirectionContext'; @@ -36,7 +38,7 @@ export const TabsIndicator = React.forwardRef(function TabIndicator( const { tabsListElement } = useTabsListContext(); - const [isMounted, setIsMounted] = React.useState(false); + const [isMounted, setIsMounted] = useState(false); const { value: activeTabValue } = useTabsRootContext(); const direction = useDirection(); @@ -45,7 +47,7 @@ export const TabsIndicator = React.forwardRef(function TabIndicator( const rerender = useForcedRerendering(); - React.useEffect(() => { + useEffect(() => { if (value != null && tabsListElement != null && typeof ResizeObserver !== 'undefined') { const resizeObserver = new ResizeObserver(rerender); diff --git a/packages/react/src/tabs/list/TabsList.tsx b/packages/react/src/tabs/list/TabsList.tsx index 9b0945e87a0..77a60569e6b 100644 --- a/packages/react/src/tabs/list/TabsList.tsx +++ b/packages/react/src/tabs/list/TabsList.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useCallback } from '@base-ui-components/utils/useCallback'; +import { useState } from '@base-ui-components/utils/useState'; import { BaseUIComponentProps, HTMLProps } from '../../utils/types'; import type { TabsRoot } from '../root/TabsRoot'; import { CompositeRoot } from '../../composite/root/CompositeRoot'; @@ -38,9 +40,9 @@ export const TabsList = React.forwardRef(function TabsList( tabActivationDirection, } = useTabsRootContext(); - const [highlightedTabIndex, setHighlightedTabIndex] = React.useState(0); + const [highlightedTabIndex, setHighlightedTabIndex] = useState(0); - const [tabsListElement, setTabsListElement] = React.useState(null); + const [tabsListElement, setTabsListElement] = useState(null); const detectActivationDirection = useActivationDirectionDetector( value, // the old value @@ -129,7 +131,7 @@ function useActivationDirectionDetector( tabsListElement: HTMLElement | null, getTabElement: (selectedValue: any) => HTMLElement | null, ): (newValue: any) => TabsTab.ActivationDirection { - const [previousTabEdge, setPreviousTabEdge] = React.useState(null); + const [previousTabEdge, setPreviousTabEdge] = useState(null); useIsoLayoutEffect(() => { // Whenever orientation changes, reset the state. @@ -148,7 +150,7 @@ function useActivationDirectionDetector( setPreviousTabEdge(orientation === 'horizontal' ? left : top); }, [orientation, getTabElement, tabsListElement, activeTabValue]); - return React.useCallback( + return useCallback( (newValue: any) => { if (newValue === activeTabValue) { return 'none'; diff --git a/packages/react/src/tabs/root/TabsRoot.tsx b/packages/react/src/tabs/root/TabsRoot.tsx index d1b193e2181..99e380e01c8 100644 --- a/packages/react/src/tabs/root/TabsRoot.tsx +++ b/packages/react/src/tabs/root/TabsRoot.tsx @@ -2,6 +2,9 @@ import * as React from 'react'; import { useControlled } from '@base-ui-components/utils/useControlled'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; +import { useState } from '@base-ui-components/utils/useState'; import type { BaseUIComponentProps, Orientation as BaseOrientation } from '../../utils/types'; import { useRenderElement } from '../../utils/useRenderElement'; import { CompositeList } from '../../composite/list/CompositeList'; @@ -36,7 +39,7 @@ export const TabsRoot = React.forwardRef(function TabsRoot( const direction = useDirection(); - const tabPanelRefs = React.useRef<(HTMLElement | null)[]>([]); + const tabPanelRefs = useRef<(HTMLElement | null)[]>([]); const [value, setValue] = useControlled({ controlled: valueProp, @@ -45,15 +48,15 @@ export const TabsRoot = React.forwardRef(function TabsRoot( state: 'value', }); - const [tabPanelMap, setTabPanelMap] = React.useState( + const [tabPanelMap, setTabPanelMap] = useState( () => new Map | null>(), ); - const [tabMap, setTabMap] = React.useState( + const [tabMap, setTabMap] = useState( () => new Map | null>(), ); const [tabActivationDirection, setTabActivationDirection] = - React.useState('none'); + useState('none'); const onValueChange = useStableCallback( (newValue: TabsTab.Value, eventDetails: TabsRoot.ChangeEventDetails) => { @@ -69,7 +72,7 @@ export const TabsRoot = React.forwardRef(function TabsRoot( ); // get the `id` attribute of to set as the value of `aria-controls` on - const getTabPanelIdByTabValueOrIndex = React.useCallback( + const getTabPanelIdByTabValueOrIndex = useCallback( (tabValue: TabsTab.Value | undefined, index: number) => { if (tabValue === undefined && index < 0) { return undefined; @@ -97,7 +100,7 @@ export const TabsRoot = React.forwardRef(function TabsRoot( ); // get the `id` attribute of to set as the value of `aria-labelledby` on - const getTabIdByPanelValueOrIndex = React.useCallback( + const getTabIdByPanelValueOrIndex = useCallback( (tabPanelValue: TabsTab.Value | undefined, index: number) => { if (tabPanelValue === undefined && index < 0) { return undefined; @@ -129,7 +132,7 @@ export const TabsRoot = React.forwardRef(function TabsRoot( ); // used in `useActivationDirectionDetector` for setting data-activation-direction - const getTabElementBySelectedValue = React.useCallback( + const getTabElementBySelectedValue = useCallback( (selectedValue: TabsTab.Value | undefined): HTMLElement | null => { if (selectedValue === undefined) { return null; diff --git a/packages/react/src/tabs/tab/TabsTab.tsx b/packages/react/src/tabs/tab/TabsTab.tsx index f83749c18ef..455e52d286b 100644 --- a/packages/react/src/tabs/tab/TabsTab.tsx +++ b/packages/react/src/tabs/tab/TabsTab.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { ownerDocument } from '@base-ui-components/utils/owner'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { useBaseUiId } from '../../utils/useBaseUiId'; import { useRenderElement } from '../../utils/useRenderElement'; import type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types'; @@ -76,7 +77,7 @@ export const TabsTab = React.forwardRef(function TabsTab( return valueProp === activeTabValue; }, [index, activeTabValue, valueProp]); - const isNavigatingRef = React.useRef(false); + const isNavigatingRef = useRef(false); // Keep the highlighted item in sync with the currently active tab // when the value prop changes externally (controlled mode) @@ -112,8 +113,8 @@ export const TabsTab = React.forwardRef(function TabsTab( const tabPanelId = index > -1 ? getTabPanelIdByTabValueOrIndex(valueProp, index) : undefined; - const isPressingRef = React.useRef(false); - const isMainButtonRef = React.useRef(false); + const isPressingRef = useRef(false); + const isMainButtonRef = useRef(false); function onClick(event: React.MouseEvent) { if (active || disabled) { diff --git a/packages/react/src/toast/content/ToastContent.tsx b/packages/react/src/toast/content/ToastContent.tsx index ffe59ecfd79..3b8e7b2b973 100644 --- a/packages/react/src/toast/content/ToastContent.tsx +++ b/packages/react/src/toast/content/ToastContent.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import type { BaseUIComponentProps } from '../../utils/types'; import { useToastRootContext } from '../root/ToastRootContext'; import { useRenderElement } from '../../utils/useRenderElement'; @@ -19,7 +20,7 @@ export const ToastContent = React.forwardRef(function ToastContent( const { visibleIndex, expanded, recalculateHeight } = useToastRootContext(); - const contentRef = React.useRef(null); + const contentRef = useRef(null); useIsoLayoutEffect(() => { const node = contentRef.current; diff --git a/packages/react/src/toast/positioner/ToastPositioner.tsx b/packages/react/src/toast/positioner/ToastPositioner.tsx index 6f08eab7fa3..13277006f84 100644 --- a/packages/react/src/toast/positioner/ToastPositioner.tsx +++ b/packages/react/src/toast/positioner/ToastPositioner.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useState } from '@base-ui-components/utils/useState'; import { isElement } from '@floating-ui/utils/dom'; import { useAnchorPositioning, type Side, type Align } from '../../utils/useAnchorPositioning'; import type { BaseUIComponentProps, HTMLProps } from '../../utils/types'; @@ -49,7 +50,7 @@ export const ToastPositioner = React.forwardRef(function ToastPositioner( ...elementProps } = props; - const [positionerElement, setPositionerElement] = React.useState(null); + const [positionerElement, setPositionerElement] = useState(null); const domIndex = React.useMemo(() => toasts.indexOf(toast), [toast, toasts]); const visibleIndex = React.useMemo( diff --git a/packages/react/src/toast/provider/ToastProvider.tsx b/packages/react/src/toast/provider/ToastProvider.tsx index 870691057a5..d382effc565 100644 --- a/packages/react/src/toast/provider/ToastProvider.tsx +++ b/packages/react/src/toast/provider/ToastProvider.tsx @@ -4,6 +4,9 @@ import { ownerDocument } from '@base-ui-components/utils/owner'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { generateId } from '@base-ui-components/utils/generateId'; import { Timeout } from '@base-ui-components/utils/useTimeout'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { activeElement, contains } from '../../floating-ui-react/utils'; import { ToastContext } from './ToastProviderContext'; import { isFocusVisible } from '../utils/focusVisible'; @@ -32,10 +35,10 @@ interface TimerInfo { export const ToastProvider: React.FC = function ToastProvider(props) { const { children, timeout = 5000, limit = 3, toastManager } = props; - const [toasts, setToasts] = React.useState[]>([]); - const [hovering, setHovering] = React.useState(false); - const [focused, setFocused] = React.useState(false); - const [prevFocusElement, setPrevFocusElement] = React.useState(null); + const [toasts, setToasts] = useState[]>([]); + const [hovering, setHovering] = useState(false); + const [focused, setFocused] = useState(false); + const [prevFocusElement, setPrevFocusElement] = useState(null); if (toasts.length === 0) { if (hovering) { @@ -49,10 +52,10 @@ export const ToastProvider: React.FC = function ToastProvid const expanded = hovering || focused; - const timersRef = React.useRef(new Map()); - const viewportRef = React.useRef(null); - const windowFocusedRef = React.useRef(true); - const isPausedRef = React.useRef(false); + const timersRef = useRef(new Map()); + const viewportRef = useRef(null); + const windowFocusedRef = useRef(true); + const isPausedRef = useRef(false); function handleFocusManagement(toastId: string) { const activeEl = activeElement(ownerDocument(viewportRef.current)); @@ -297,7 +300,7 @@ export const ToastProvider: React.FC = function ToastProvid }, ); - React.useEffect( + useEffect( function subscribeToToastManager() { if (!toastManager) { return undefined; diff --git a/packages/react/src/toast/root/ToastRoot.tsx b/packages/react/src/toast/root/ToastRoot.tsx index e3ee0c502c5..cff55e2489a 100644 --- a/packages/react/src/toast/root/ToastRoot.tsx +++ b/packages/react/src/toast/root/ToastRoot.tsx @@ -4,6 +4,9 @@ import { ownerDocument } from '@base-ui-components/utils/owner'; import { inertValue } from '@base-ui-components/utils/inertValue'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { activeElement, contains, getTarget } from '../../floating-ui-react/utils'; import type { BaseUIComponentProps, HTMLProps } from '../../utils/types'; import type { ToastObject as ToastObjectType } from '../useToastManager'; @@ -101,30 +104,30 @@ export const ToastRoot = React.forwardRef(function ToastRoot( const { toasts, focused, close, remove, setToasts, pauseTimers, expanded, setHovering } = useToastContext(); - const [currentSwipeDirection, setCurrentSwipeDirection] = React.useState< + const [currentSwipeDirection, setCurrentSwipeDirection] = useState< 'up' | 'down' | 'left' | 'right' | undefined >(undefined); - const [isSwiping, setIsSwiping] = React.useState(false); - const [isRealSwipe, setIsRealSwipe] = React.useState(false); - const [dragDismissed, setDragDismissed] = React.useState(false); - const [dragOffset, setDragOffset] = React.useState({ x: 0, y: 0 }); - const [initialTransform, setInitialTransform] = React.useState({ x: 0, y: 0, scale: 1 }); - const [titleId, setTitleId] = React.useState(); - const [descriptionId, setDescriptionId] = React.useState(); - const [lockedDirection, setLockedDirection] = React.useState<'horizontal' | 'vertical' | null>( + const [isSwiping, setIsSwiping] = useState(false); + const [isRealSwipe, setIsRealSwipe] = useState(false); + const [dragDismissed, setDragDismissed] = useState(false); + const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); + const [initialTransform, setInitialTransform] = useState({ x: 0, y: 0, scale: 1 }); + const [titleId, setTitleId] = useState(); + const [descriptionId, setDescriptionId] = useState(); + const [lockedDirection, setLockedDirection] = useState<'horizontal' | 'vertical' | null>( null, ); - const rootRef = React.useRef(null); - const dragStartPosRef = React.useRef({ x: 0, y: 0 }); - const initialTransformRef = React.useRef({ x: 0, y: 0, scale: 1 }); - const intendedSwipeDirectionRef = React.useRef<'up' | 'down' | 'left' | 'right' | undefined>( + const rootRef = useRef(null); + const dragStartPosRef = useRef({ x: 0, y: 0 }); + const initialTransformRef = useRef({ x: 0, y: 0, scale: 1 }); + const intendedSwipeDirectionRef = useRef<'up' | 'down' | 'left' | 'right' | undefined>( undefined, ); - const maxSwipeDisplacementRef = React.useRef(0); - const cancelledSwipeRef = React.useRef(false); - const swipeCancelBaselineRef = React.useRef({ x: 0, y: 0 }); - const isFirstPointerMoveRef = React.useRef(false); + const maxSwipeDisplacementRef = useRef(0); + const cancelledSwipeRef = useRef(false); + const swipeCancelBaselineRef = useRef({ x: 0, y: 0 }); + const isFirstPointerMoveRef = useRef(false); const domIndex = React.useMemo(() => toasts.indexOf(toast), [toast, toasts]); const visibleIndex = React.useMemo( @@ -446,7 +449,7 @@ export const ToastRoot = React.forwardRef(function ToastRoot( } } - React.useEffect(() => { + useEffect(() => { if (!swipeEnabled) { return undefined; } diff --git a/packages/react/src/toast/viewport/ToastViewport.tsx b/packages/react/src/toast/viewport/ToastViewport.tsx index 1ca6aa8e6d8..728d3ba532e 100644 --- a/packages/react/src/toast/viewport/ToastViewport.tsx +++ b/packages/react/src/toast/viewport/ToastViewport.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import { ownerDocument, ownerWindow } from '@base-ui-components/utils/owner'; import { visuallyHidden } from '@base-ui-components/utils/visuallyHidden'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import { activeElement, contains, getTarget } from '../../floating-ui-react/utils'; import { FocusGuard } from '../../utils/FocusGuard'; import type { BaseUIComponentProps, HTMLProps } from '../../utils/types'; @@ -37,8 +39,8 @@ export const ToastViewport = React.forwardRef(function ToastViewport( focused, } = useToastContext(); - const handlingFocusGuardRef = React.useRef(false); - const markedReadyForMouseLeaveRef = React.useRef(false); + const handlingFocusGuardRef = useRef(false); + const markedReadyForMouseLeaveRef = useRef(false); const numToasts = toasts.length; const frontmostHeight = toasts[0]?.height ?? 0; @@ -49,7 +51,7 @@ export const ToastViewport = React.forwardRef(function ToastViewport( ); // Listen globally for F6 so we can force-focus the viewport. - React.useEffect(() => { + useEffect(() => { if (!viewportRef.current) { return undefined; } @@ -79,7 +81,7 @@ export const ToastViewport = React.forwardRef(function ToastViewport( }; }, [pauseTimers, setFocused, setPrevFocusElement, numToasts, viewportRef]); - React.useEffect(() => { + useEffect(() => { if (!viewportRef.current || !numToasts) { return undefined; } @@ -135,7 +137,7 @@ export const ToastViewport = React.forwardRef(function ToastViewport( numToasts, ]); - React.useEffect(() => { + useEffect(() => { const viewportNode = viewportRef.current; if (!viewportNode || numToasts === 0) { return undefined; @@ -188,7 +190,7 @@ export const ToastViewport = React.forwardRef(function ToastViewport( } } - React.useEffect(() => { + useEffect(() => { if ( !windowFocusedRef.current || hasTransitioningToasts || diff --git a/packages/react/src/toolbar/root/ToolbarRoot.tsx b/packages/react/src/toolbar/root/ToolbarRoot.tsx index af45d2361f5..ef2e7dd7703 100644 --- a/packages/react/src/toolbar/root/ToolbarRoot.tsx +++ b/packages/react/src/toolbar/root/ToolbarRoot.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useState } from '@base-ui-components/utils/useState'; import { BaseUIComponentProps, Orientation as BaseOrientation, HTMLProps } from '../../utils/types'; import { CompositeRoot } from '../../composite/root/CompositeRoot'; import type { CompositeMetadata } from '../../composite/list/CompositeList'; @@ -24,7 +25,7 @@ export const ToolbarRoot = React.forwardRef(function ToolbarRoot( ...elementProps } = componentProps; - const [itemMap, setItemMap] = React.useState( + const [itemMap, setItemMap] = useState( () => new Map | null>(), ); diff --git a/packages/react/src/tooltip/root/TooltipRoot.tsx b/packages/react/src/tooltip/root/TooltipRoot.tsx index fa5d8ba9152..58b8c28b04e 100644 --- a/packages/react/src/tooltip/root/TooltipRoot.tsx +++ b/packages/react/src/tooltip/root/TooltipRoot.tsx @@ -1,6 +1,9 @@ 'use client'; import * as React from 'react'; +import * as fastHooks from '@base-ui-components/utils/fastHooks'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { TooltipRootContext } from './TooltipRootContext'; import { useClientPoint, @@ -28,7 +31,9 @@ import { REASONS } from '../../utils/reasons'; * * Documentation: [Base UI Tooltip](https://base-ui.com/react/components/tooltip) */ -export function TooltipRoot(props: TooltipRoot.Props) { +export const TooltipRoot = fastHooks.createComponent(function TooltipRoot( + props: TooltipRoot.Props, +) { const { disabled = false, defaultOpen = false, @@ -86,7 +91,7 @@ export function TooltipRoot(props: TooltipRoot.Props) { // 2) Closing because another tooltip opened (reason === 'none') // Otherwise, allow the animation to play. In particular, do not disable animations // during the 'ending' phase unless it's due to a sibling opening. - const previousInstantTypeRef = React.useRef(null); + const previousInstantTypeRef = useRef(null); useIsoLayoutEffect(() => { if ( (transitionStatus === 'ending' && lastOpenChangeReason === REASONS.none) || @@ -113,7 +118,7 @@ export function TooltipRoot(props: TooltipRoot.Props) { } }, [store, activeTriggerId, open]); - const handleImperativeClose = React.useCallback(() => { + const handleImperativeClose = useCallback(() => { store.setOpen(false, createTooltipEventDetails(store, REASONS.imperativeAction)); }, [store]); @@ -157,7 +162,7 @@ export function TooltipRoot(props: TooltipRoot.Props) { {typeof children === 'function' ? children({ payload }) : children} ); -} +}); function createTooltipEventDetails

( store: TooltipStore

, diff --git a/packages/react/src/tooltip/trigger/TooltipTrigger.tsx b/packages/react/src/tooltip/trigger/TooltipTrigger.tsx index 0d3382a5f9b..e48fe8c8b02 100644 --- a/packages/react/src/tooltip/trigger/TooltipTrigger.tsx +++ b/packages/react/src/tooltip/trigger/TooltipTrigger.tsx @@ -1,5 +1,7 @@ 'use client'; import * as React from 'react'; +import { useState } from '@base-ui-components/utils/useState'; +import * as fastHooks from '@base-ui-components/utils/fastHooks'; import { useTooltipRootContext } from '../root/TooltipRootContext'; import type { BaseUIComponentProps } from '../../utils/types'; import { triggerOpenStateMapping } from '../../utils/popupStateMapping'; @@ -18,7 +20,7 @@ import { OPEN_DELAY } from '../utils/constants'; * * Documentation: [Base UI Tooltip](https://base-ui.com/react/components/tooltip) */ -export const TooltipTrigger = React.forwardRef(function TooltipTrigger( +export const TooltipTrigger = fastHooks.createComponent(function TooltipTrigger( componentProps: TooltipTrigger.Props, forwardedRef: React.ForwardedRef, ) { @@ -47,7 +49,7 @@ export const TooltipTrigger = React.forwardRef(function TooltipTrigger( const floatingRootContext = store.useState('floatingRootContext'); const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId); - const [triggerElement, setTriggerElement] = React.useState(null); + const [triggerElement, setTriggerElement] = useState(null); const delayWithDefault = delay ?? OPEN_DELAY; const closeDelayWithDefault = closeDelay ?? 0; diff --git a/packages/react/src/tooltip/viewport/TooltipViewport.tsx b/packages/react/src/tooltip/viewport/TooltipViewport.tsx index a51221d410b..54a1eacba5d 100644 --- a/packages/react/src/tooltip/viewport/TooltipViewport.tsx +++ b/packages/react/src/tooltip/viewport/TooltipViewport.tsx @@ -4,6 +4,9 @@ import { useAnimationFrame } from '@base-ui-components/utils/useAnimationFrame'; import { usePreviousValue } from '@base-ui-components/utils/usePreviousValue'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { useTooltipRootContext } from '../root/TooltipRootContext'; import { BaseUIComponentProps } from '../../utils/types'; import { useAnimationsFinished } from '../../utils/useAnimationsFinished'; @@ -43,23 +46,23 @@ export const TooltipViewport = React.forwardRef(function TooltipViewport( const previousActiveTrigger = usePreviousValue(open ? activeTrigger : null); - const capturedNodeRef = React.useRef(null); - const [previousContentNode, setPreviousContentNode] = React.useState(null); + const capturedNodeRef = useRef(null); + const [previousContentNode, setPreviousContentNode] = useState(null); - const [newTriggerOffset, setNewTriggerOffset] = React.useState(null); + const [newTriggerOffset, setNewTriggerOffset] = useState(null); - const currentContainerRef = React.useRef(null); - const previousContainerRef = React.useRef(null); + const currentContainerRef = useRef(null); + const previousContainerRef = useRef(null); const onAnimationsFinished = useAnimationsFinished(currentContainerRef, true, false); const cleanupTimeout = useAnimationFrame(); - const [previousContentDimensions, setPreviousContentDimensions] = React.useState<{ + const [previousContentDimensions, setPreviousContentDimensions] = useState<{ width: number; height: number; } | null>(null); - const [showStartingStyleAttribute, setShowStartingStyleAttribute] = React.useState(false); + const [showStartingStyleAttribute, setShowStartingStyleAttribute] = useState(false); const handleMeasureLayout = useStableCallback(() => { currentContainerRef.current?.style.setProperty('animation', 'none'); @@ -84,7 +87,7 @@ export const TooltipViewport = React.forwardRef(function TooltipViewport( } }); - React.useEffect(() => { + useEffect(() => { floatingContext.context.events.on('measure-layout', handleMeasureLayout); floatingContext.context.events.on('measure-layout-complete', handleMeasureLayoutComplete); @@ -94,7 +97,7 @@ export const TooltipViewport = React.forwardRef(function TooltipViewport( }; }, [floatingContext, handleMeasureLayout, handleMeasureLayoutComplete]); - const lastHandledTriggerRef = React.useRef(null); + const lastHandledTriggerRef = useRef(null); useIsoLayoutEffect(() => { // When a trigger changes, set the captured children HTML to state, diff --git a/packages/react/src/unstable-no-ssr/NoSsr.tsx b/packages/react/src/unstable-no-ssr/NoSsr.tsx index 17703ec90f8..61ef7382531 100644 --- a/packages/react/src/unstable-no-ssr/NoSsr.tsx +++ b/packages/react/src/unstable-no-ssr/NoSsr.tsx @@ -1,6 +1,8 @@ 'use client'; import * as React from 'react'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useState } from '@base-ui-components/utils/useState'; import { NoSsrProps } from './NoSsr.types'; /** @@ -17,7 +19,7 @@ import { NoSsrProps } from './NoSsr.types'; */ export function NoSsr(props: NoSsrProps): React.JSX.Element { const { children, defer = false, fallback = null } = props; - const [mountedState, setMountedState] = React.useState(false); + const [mountedState, setMountedState] = useState(false); useIsoLayoutEffect(() => { if (!defer) { @@ -25,7 +27,7 @@ export function NoSsr(props: NoSsrProps): React.JSX.Element { } }, [defer]); - React.useEffect(() => { + useEffect(() => { if (defer) { setMountedState(true); } diff --git a/packages/react/src/unstable-use-media-query/index.ts b/packages/react/src/unstable-use-media-query/index.ts index 446d1c154ba..e874fbe30a3 100644 --- a/packages/react/src/unstable-use-media-query/index.ts +++ b/packages/react/src/unstable-use-media-query/index.ts @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; export function useMediaQuery(query: string, options: useMediaQuery.Options): boolean { @@ -18,7 +19,7 @@ export function useMediaQuery(query: string, options: useMediaQuery.Options): bo noSsr = false, } = options; - const getDefaultSnapshot = React.useCallback(() => defaultMatches, [defaultMatches]); + const getDefaultSnapshot = useCallback(() => defaultMatches, [defaultMatches]); const getServerSnapshot = React.useMemo(() => { if (noSsr && matchMedia) { diff --git a/packages/react/src/use-button/useButton.ts b/packages/react/src/use-button/useButton.ts index a7f5477eea6..c4e5865204e 100644 --- a/packages/react/src/use-button/useButton.ts +++ b/packages/react/src/use-button/useButton.ts @@ -4,6 +4,9 @@ import { isHTMLElement } from '@floating-ui/utils/dom'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { error } from '@base-ui-components/utils/error'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { makeEventPreventable, mergeProps } from '../merge-props'; import { useCompositeRootContext } from '../composite/root/CompositeRootContext'; import { BaseUIEvent, HTMLProps } from '../utils/types'; @@ -17,7 +20,7 @@ export function useButton(parameters: useButton.Parameters = {}): useButton.Retu native: isNativeButton = true, } = parameters; - const elementRef = React.useRef(null); + const elementRef = useRef(null); const isCompositeItem = useCompositeRootContext(true) !== undefined; @@ -36,7 +39,7 @@ export function useButton(parameters: useButton.Parameters = {}): useButton.Retu if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line react-hooks/rules-of-hooks - React.useEffect(() => { + useEffect(() => { if (!elementRef.current) { return; } @@ -61,7 +64,7 @@ export function useButton(parameters: useButton.Parameters = {}): useButton.Retu // } /> // the `disabled` prop needs to pass through 2 `useButton`s then finally // delete the `disabled` attribute from DOM - const updateDisabled = React.useCallback(() => { + const updateDisabled = useCallback(() => { const element = elementRef.current; if (!isButtonElement(element)) { @@ -80,7 +83,7 @@ export function useButton(parameters: useButton.Parameters = {}): useButton.Retu useIsoLayoutEffect(updateDisabled, [updateDisabled]); - const getButtonProps = React.useCallback( + const getButtonProps = useCallback( (externalProps: GenericButtonProps = {}) => { const { onClick: externalOnClick, diff --git a/packages/react/src/utils/FocusGuard.tsx b/packages/react/src/utils/FocusGuard.tsx index bd5a9a57f09..9af7904a235 100644 --- a/packages/react/src/utils/FocusGuard.tsx +++ b/packages/react/src/utils/FocusGuard.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { isSafari } from '@base-ui-components/utils/detectBrowser'; import { visuallyHidden } from '@base-ui-components/utils/visuallyHidden'; +import { useState } from '@base-ui-components/utils/useState'; /** * @internal @@ -11,7 +12,7 @@ export const FocusGuard = React.forwardRef(function FocusGuard( props: React.ComponentPropsWithoutRef<'span'>, ref: React.ForwardedRef, ) { - const [role, setRole] = React.useState<'button' | undefined>(); + const [role, setRole] = useState<'button' | undefined>(); useIsoLayoutEffect(() => { if (isSafari) { diff --git a/packages/react/src/utils/interactions/useFocusWithDelay.ts b/packages/react/src/utils/interactions/useFocusWithDelay.ts index 8cbf4ca0ae6..a9b692ee9d1 100644 --- a/packages/react/src/utils/interactions/useFocusWithDelay.ts +++ b/packages/react/src/utils/interactions/useFocusWithDelay.ts @@ -2,6 +2,8 @@ import * as React from 'react'; import { getWindow, isHTMLElement } from '@floating-ui/utils/dom'; import { useTimeout } from '@base-ui-components/utils/useTimeout'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; import type { FloatingRootContext, ElementProps } from '../../floating-ui-react'; import { createChangeEventDetails } from '../createBaseUIEventDetails'; import { REASONS } from '../reasons'; @@ -23,9 +25,9 @@ export function useFocusWithDelay( const { delay } = props; const timeout = useTimeout(); - const blockFocusRef = React.useRef(false); + const blockFocusRef = useRef(false); - React.useEffect(() => { + useEffect(() => { // TODO: remove domReference from dependencies or split this hook into trigger/popup hooks. const win = getWindow(domReference); diff --git a/packages/react/src/utils/popups/popupStoreUtils.ts b/packages/react/src/utils/popups/popupStoreUtils.ts index 9a804737478..7cc6975c8c7 100644 --- a/packages/react/src/utils/popups/popupStoreUtils.ts +++ b/packages/react/src/utils/popups/popupStoreUtils.ts @@ -2,6 +2,8 @@ import * as React from 'react'; import { ReactStore } from '@base-ui-components/utils/store'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useCallback } from '@base-ui-components/utils/useCallback'; import { useTransitionStatus } from '../useTransitionStatus'; import { useOpenChangeComplete } from '../useOpenChangeComplete'; import { @@ -21,9 +23,9 @@ export function useTriggerRegistration>( store: ReactStore, PopupStoreSelectors>, ) { // Keep track of the currently registered element to unregister it on unmount or id change. - const registeredElementId = React.useRef(null); + const registeredElementId = useRef(null); - return React.useCallback( + return useCallback( (element: Element | null) => { if (id === undefined) { return undefined; diff --git a/packages/react/src/utils/useAnchorPositioning.ts b/packages/react/src/utils/useAnchorPositioning.ts index 25208407e8c..e9a79046a4e 100644 --- a/packages/react/src/utils/useAnchorPositioning.ts +++ b/packages/react/src/utils/useAnchorPositioning.ts @@ -5,6 +5,9 @@ import { ownerDocument } from '@base-ui-components/utils/owner'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useEffect } from '@base-ui-components/utils/useEffect'; +import { useRef } from '@base-ui-components/utils/useRef'; +import { useState } from '@base-ui-components/utils/useState'; import { autoUpdate, flip, @@ -131,7 +134,7 @@ export function useAnchorPositioning( externalTree, } = params; - const [mountSide, setMountSide] = React.useState(null); + const [mountSide, setMountSide] = useState(null); if (!mounted && mountSide !== null) { setMountSide(null); @@ -204,7 +207,7 @@ export function useAnchorPositioning( // Using a ref assumes that the arrow element is always present in the DOM for the lifetime of the // popup. If this assumption ends up being false, we can switch to state to manage the arrow's // presence. - const arrowRef = React.useRef(null); + const arrowRef = useRef(null); // Keep these reactive if they're not functions const sideOffsetRef = useValueAsRef(sideOffset); @@ -430,7 +433,7 @@ export function useAnchorPositioning( [adaptiveOrigin, resolvedPosition, sideX, x, sideY, y, originalFloatingStyles], ); - const registeredPositionReferenceRef = React.useRef(null); + const registeredPositionReferenceRef = useRef(null); useIsoLayoutEffect(() => { if (!mounted) { @@ -449,7 +452,7 @@ export function useAnchorPositioning( } }, [mounted, refs, anchorDep, anchorValueRef]); - React.useEffect(() => { + useEffect(() => { if (!mounted) { return; } @@ -468,7 +471,7 @@ export function useAnchorPositioning( } }, [mounted, refs, anchorDep, anchorValueRef]); - React.useEffect(() => { + useEffect(() => { if (keepMounted && mounted && elements.domReference && elements.floating) { return autoUpdate(elements.domReference, elements.floating, update, autoUpdateOptions); } diff --git a/packages/react/src/utils/useMixedToggleClickHander.ts b/packages/react/src/utils/useMixedToggleClickHander.ts index 40f65282e0e..a1925c53f75 100644 --- a/packages/react/src/utils/useMixedToggleClickHander.ts +++ b/packages/react/src/utils/useMixedToggleClickHander.ts @@ -1,5 +1,6 @@ import * as React from 'react'; import { ownerDocument } from '@base-ui-components/utils/owner'; +import { useRef } from '@base-ui-components/utils/useRef'; import { BaseUIEvent } from './types'; import { EMPTY_OBJECT } from './constants'; @@ -10,7 +11,7 @@ import { EMPTY_OBJECT } from './constants'; */ export function useMixedToggleClickHandler(params: useMixedToggleClickHandler.Parameters) { const { enabled = true, mouseDownAction, open } = params; - const ignoreClickRef = React.useRef(false); + const ignoreClickRef = useRef(false); return React.useMemo(() => { if (!enabled) { diff --git a/packages/react/src/utils/useOpenChangeComplete.tsx b/packages/react/src/utils/useOpenChangeComplete.tsx index fa3c2ce206a..bba69fbd11e 100644 --- a/packages/react/src/utils/useOpenChangeComplete.tsx +++ b/packages/react/src/utils/useOpenChangeComplete.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef'; +import { useEffect } from '@base-ui-components/utils/useEffect'; import { useAnimationsFinished } from './useAnimationsFinished'; /** @@ -14,7 +15,7 @@ export function useOpenChangeComplete(parameters: useOpenChangeComplete.Paramete const onComplete = useStableCallback(onCompleteParam); const runOnceAnimationsFinish = useAnimationsFinished(ref, open); - React.useEffect(() => { + useEffect(() => { if (!enabled) { return; } diff --git a/packages/react/src/utils/useOpenInteractionType.ts b/packages/react/src/utils/useOpenInteractionType.ts index c10220041bd..688746787ad 100644 --- a/packages/react/src/utils/useOpenInteractionType.ts +++ b/packages/react/src/utils/useOpenInteractionType.ts @@ -1,6 +1,8 @@ 'use client'; import * as React from 'react'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useCallback } from '@base-ui-components/utils/useCallback'; +import { useState } from '@base-ui-components/utils/useState'; import { InteractionType, useEnhancedClickHandler, @@ -12,7 +14,7 @@ import { * @param open The open state of the component. */ export function useOpenInteractionType(open: boolean) { - const [openMethod, setOpenMethod] = React.useState(null); + const [openMethod, setOpenMethod] = useState(null); const handleTriggerClick = useStableCallback( (_: React.MouseEvent, interactionType: InteractionType) => { @@ -22,7 +24,7 @@ export function useOpenInteractionType(open: boolean) { }, ); - const reset = React.useCallback(() => { + const reset = useCallback(() => { setOpenMethod(null); }, []); diff --git a/packages/react/src/utils/usePopupAutoResize.ts b/packages/react/src/utils/usePopupAutoResize.ts index 910cbe4c196..a4c09e0ff00 100644 --- a/packages/react/src/utils/usePopupAutoResize.ts +++ b/packages/react/src/utils/usePopupAutoResize.ts @@ -2,6 +2,7 @@ import * as React from 'react'; import { useAnimationFrame } from '@base-ui-components/utils/useAnimationFrame'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useRef } from '@base-ui-components/utils/useRef'; import { useAnimationsFinished } from './useAnimationsFinished'; import { getCssDimensions } from './getCssDimensions'; import { Dimensions } from '../floating-ui-react/types'; @@ -24,10 +25,10 @@ export function usePopupAutoResize(parameters: UsePopupAutoResizeParameters) { onMeasureLayoutComplete: onMeasureLayoutCompleteParam, } = parameters; - const isInitialRender = React.useRef(true); + const isInitialRender = useRef(true); const runOnceAnimationsFinish = useAnimationsFinished(popupElement, true, false); const animationFrame = useAnimationFrame(); - const previousDimensionsRef = React.useRef(null); + const previousDimensionsRef = useRef(null); const onMeasureLayout = useStableCallback(onMeasureLayoutParam); const onMeasureLayoutComplete = useStableCallback(onMeasureLayoutCompleteParam); diff --git a/packages/react/src/utils/useTransitionStatus.ts b/packages/react/src/utils/useTransitionStatus.ts index 72ea5a22466..49d7f86e37a 100644 --- a/packages/react/src/utils/useTransitionStatus.ts +++ b/packages/react/src/utils/useTransitionStatus.ts @@ -3,6 +3,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { AnimationFrame } from '@base-ui-components/utils/useAnimationFrame'; +import { useState } from '@base-ui-components/utils/useState'; export type TransitionStatus = 'starting' | 'ending' | 'idle' | undefined; @@ -16,10 +17,10 @@ export function useTransitionStatus( enableIdleState: boolean = false, deferEndingState: boolean = false, ) { - const [transitionStatus, setTransitionStatus] = React.useState( + const [transitionStatus, setTransitionStatus] = useState( open && enableIdleState ? 'idle' : undefined, ); - const [mounted, setMounted] = React.useState(open); + const [mounted, setMounted] = useState(open); if (open && !mounted) { setMounted(true); diff --git a/packages/react/src/utils/useValueChanged.ts b/packages/react/src/utils/useValueChanged.ts index b39f231a2f2..a5998e7bc60 100644 --- a/packages/react/src/utils/useValueChanged.ts +++ b/packages/react/src/utils/useValueChanged.ts @@ -1,9 +1,10 @@ import * as React from 'react'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; +import { useRef } from '@base-ui-components/utils/useRef'; export function useValueChanged(value: T, onChange: (previousValue: T) => void) { - const valueRef = React.useRef(value); + const valueRef = useRef(value); const onChangeCallback = useStableCallback(onChange); useIsoLayoutEffect(() => { diff --git a/packages/utils/src/fastHooks.ts b/packages/utils/src/fastHooks.ts new file mode 100644 index 00000000000..7aad401a8e6 --- /dev/null +++ b/packages/utils/src/fastHooks.ts @@ -0,0 +1,365 @@ +import * as React from 'react'; + +type Effect = { + cleanup: (() => void) | undefined; + create: () => (() => void) | void; + deps: unknown[] | undefined; + renderedDeps: unknown[] | undefined; + didChange: boolean; +}; + +type EffectContext = { + index: number; + data: Effect[]; +}; + +type RefData = { + current: T; +}; + +type RefContext = { + index: number; + data: RefData[]; +}; + +type CallbackData = { + callback: T; + deps: readonly unknown[] | undefined; +}; + +type CallbackContext = { + index: number; + data: CallbackData[]; +}; + +type StateData = [T, React.Dispatch>]; + +type StateContext = { + tick: number; + setTick: React.Dispatch>; + index: number; + data: StateData[]; +}; + +type Instance = { + didInitialize: boolean; + useEffect: EffectContext; + useLayoutEffect: EffectContext; + useInsertionEffect: EffectContext; + useRef: RefContext; + useCallback: CallbackContext; + useState: StateContext; +}; + +let currentInstance: Instance | undefined = undefined; + +const emptySetState = (_: React.SetStateAction) => { + throw new Error('Function not implemented.'); +}; + +export function createInstance(): Instance { + return { + didInitialize: false, + useEffect: { + index: 0, + data: [], + }, + useLayoutEffect: { + index: 0, + data: [], + }, + useInsertionEffect: { + index: 0, + data: [], + }, + useRef: { + index: 0, + data: [], + }, + useCallback: { + index: 0, + data: [], + }, + useState: { + index: 0, + data: [], + tick: 0, + setTick: emptySetState, + }, + }; +} + +export function getContext() { + return currentInstance; +} + +export function setContext(context: Instance | undefined): void { + currentInstance = context; +} + +export function use(): Disposable { + const instance = useRefWithInit(createInstance).current; + + currentInstance = instance; + + instance.useEffect.index = 0; + instance.useLayoutEffect.index = 0; + instance.useInsertionEffect.index = 0; + instance.useRef.index = 0; + instance.useCallback.index = 0; + instance.useState.index = 0; + + return { + [Symbol.dispose]() { + instance.didInitialize = true; + currentInstance = undefined; + }, + }; +} + +export function createComponent

( + fn: (props: P, forwardedRef: React.Ref) => React.ReactNode, +) { + const Wrapped = React.forwardRef(((props: P, forwardedRef: React.Ref) => { + const context = useRefWithInit(createInstance).current; + + let result; + try { + currentInstance = context; + + context.useEffect.index = 0; + context.useLayoutEffect.index = 0; + context.useInsertionEffect.index = 0; + context.useRef.index = 0; + context.useCallback.index = 0; + context.useState.index = 0; + + result = fn(props, forwardedRef); + + context.didInitialize = true; + } catch (error) { + throw error; + } finally { + currentInstance = undefined; + } + + return result; + }) as any); + Wrapped.displayName = (fn as any).displayName || fn.name; + + return Wrapped; +} + +export const createUseEffect = (name: 'useEffect' | 'useLayoutEffect' | 'useInsertionEffect') => { + const reactUseEffect = + name === 'useEffect' + ? React.useEffect + : name === 'useLayoutEffect' + ? React.useLayoutEffect + : React.useInsertionEffect; + + const useEffect = (create: () => (() => void) | void, deps?: unknown[]): void => { + const context = currentInstance?.[name]; + + if (!context) { + return reactUseEffect(create, deps); + } + + if (currentInstance!.didInitialize === false) { + context.data.push({ + create, + cleanup: undefined, + deps, + renderedDeps: undefined, + didChange: true, + }); + } else { + const effect = context.data[context.index]; + + effect.create = create; + effect.deps = deps; + } + + context.index += 1; + + if (context.index === 1) { + reactUseEffect(() => { + for (const effect of context.data) { + if (effect.didChange) { + effect.didChange = false; + effect.cleanup = effect.create() as any; + effect.renderedDeps = effect.deps; + } + } + return () => { + for (const effect of context.data) { + const previousDeps = effect.renderedDeps; + const currentDeps = effect.deps; + + effect.didChange = + effect.didChange || + currentDeps === undefined || + previousDeps === undefined || + areDepsEqual(previousDeps, currentDeps) === false; + + if (effect.didChange) { + effect.cleanup?.(); + } + } + }; + }); + } + }; + + return useEffect; +}; + +function areDepsEqual(depsA: readonly unknown[], depsB: readonly unknown[]): boolean { + if (depsA.length !== depsB.length) { + return false; + } + + for (let i = 0; i < depsA.length; i++) { + if (Object.is(depsA[i], depsB[i]) === false) { + return false; + } + } + + return true; +} + +export const createUseRef = () => { + // Use the same signature as React.useRef + const useRef = ( + initialValue?: T | null, + ): React.MutableRefObject | React.RefObject => { + const context = currentInstance?.useRef; + + if (!context) { + // eslint-disable-next-line react-hooks/rules-of-hooks + return React.useRef(initialValue) as React.MutableRefObject; + } + + if (currentInstance!.didInitialize === false) { + const refData: RefData = { current: initialValue }; + context.data.push(refData as RefData); + context.index += 1; + return refData as React.MutableRefObject; + } + + const refData = context.data[context.index] as RefData; + context.index += 1; + return refData as React.MutableRefObject; + }; + + return useRef as typeof React.useRef; +}; + +export const createUseCallback = () => { + function useCallback(callback: T, deps: React.DependencyList): T { + const context = currentInstance?.useCallback; + + if (!context) { + // eslint-disable-next-line react-hooks/rules-of-hooks + return React.useCallback(callback, deps); + } + + if (currentInstance!.didInitialize === false) { + const callbackData: CallbackData = { callback, deps }; + context.data.push(callbackData as CallbackData); + context.index += 1; + return callback; + } + + const callbackData = context.data[context.index] as CallbackData; + const previousDeps = callbackData.deps; + + if ( + previousDeps === undefined || + areDepsEqual(previousDeps as unknown[], deps as unknown[]) === false + ) { + callbackData.callback = callback; + callbackData.deps = deps; + } + + context.index += 1; + return callbackData.callback; + } + + return useCallback; +}; + +export const createUseState = () => { + function useState(initialState: S | (() => S)): [S, React.Dispatch>]; + function useState(): [ + S | undefined, + React.Dispatch>, + ]; + function useState( + initialState?: S | (() => S), + ): [S | undefined, React.Dispatch>] { + const context = currentInstance?.useState; + + if (!context) { + // eslint-disable-next-line react-hooks/rules-of-hooks + return React.useState(initialState); + } + + if (context.index === 0) { + const [tick, setTick] = React.useState(0); + context.tick = tick; + context.setTick = setTick; + } + + if (currentInstance!.didInitialize === false) { + const index = context.index; + + const value = typeof initialState === 'function' ? (initialState as () => S)() : initialState; + + const setValue = (action: React.SetStateAction) => { + const previousData = context.data[index]; + + const previousValue = previousData[0]; + const previousSetValue = previousData[1]; + + const newValue = + typeof action === 'function' + ? (action as (prev: S | undefined) => S | undefined)(previousValue as any) + : action; + + if (Object.is(previousValue, newValue) === false) { + const nextData = [newValue, previousSetValue] as StateData; + context.data[index] = nextData as any; + + context.tick = (context.tick + 1) >> 0; + context.setTick(context.tick); + } + }; + + const stateData: StateData = [value, setValue]; + + context.data.push(stateData as StateData); + context.index += 1; + + return stateData; + } + + const stateData = context.data[context.index] as StateData; + context.index += 1; + + return stateData; + } + + return useState; +}; + +const UNINITIALIZED = {}; + +/* We need to re-implement it here to avoid circular dependencies */ +function useRefWithInit(init: (arg?: unknown) => unknown, initArg?: unknown) { + const ref = React.useRef(UNINITIALIZED as any); + if (ref.current === UNINITIALIZED) { + ref.current = init(initArg); + } + return ref; +} diff --git a/packages/utils/src/store/ReactStore.ts b/packages/utils/src/store/ReactStore.ts index b397aa170ec..b80e9146fb7 100644 --- a/packages/utils/src/store/ReactStore.ts +++ b/packages/utils/src/store/ReactStore.ts @@ -3,6 +3,7 @@ import * as React from 'react'; import { Store } from './Store'; import { useStore } from './useStore'; +import { useRef } from '../useRef'; import { useStableCallback } from '../useStableCallback'; import { useIsoLayoutEffect } from '../useIsoLayoutEffect'; import { NOOP } from '../empty'; @@ -90,7 +91,7 @@ export class ReactStore< if (process.env.NODE_ENV !== 'production') { // Check that an object with the same shape is passed on every render React.useDebugValue(statePart, (p) => Object.keys(p)); - const keys = React.useRef>( + const keys = useRef>( Object.keys(statePart) as Array, ).current; @@ -262,7 +263,7 @@ export class ReactStore< * @param key Key of the state to set. */ public useStateSetter(key: keyof State) { - const ref = React.useRef<(v: Value) => void>(undefined as any); + const ref = useRef<(v: Value) => void>(undefined as any); if (ref.current === undefined) { ref.current = (value: Value) => { this.set(key, value); diff --git a/packages/utils/src/store/StoreInspector.tsx b/packages/utils/src/store/StoreInspector.tsx index 4e6f182286a..4527ab54719 100644 --- a/packages/utils/src/store/StoreInspector.tsx +++ b/packages/utils/src/store/StoreInspector.tsx @@ -1,6 +1,8 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Store } from './Store'; +import { useState } from '../useState'; +import { useRef } from '../useRef'; import { useForcedRerendering } from '../useForcedRerendering'; import { useStableCallback } from '../useStableCallback'; import { useAnimationFrame } from '../useAnimationFrame'; @@ -136,7 +138,7 @@ export interface StoreInspectorProps { */ export function StoreInspector(props: StoreInspectorProps) { const { store, title, additionalData, defaultOpen = false } = props; - const [open, setOpen] = React.useState(defaultOpen); + const [open, setOpen] = useState(defaultOpen); return ( @@ -289,16 +291,16 @@ interface WindowProps { * Handles all the pointer events for dragging and resizing internally. */ function Window({ title, onClose, children, headerActions }: WindowProps) { - const rootRef = React.useRef(null); - const headerRef = React.useRef(null); - const resizeHandleRef = React.useRef(null); + const rootRef = useRef(null); + const headerRef = useRef(null); + const resizeHandleRef = useRef(null); const raf = useAnimationFrame(); const minWidth = 160; const minHeight = 52; // Track position when user drags the window - const [position, setPosition] = React.useState<{ left: number; top: number } | null>(null); - const dragStateRef = React.useRef<{ + const [position, setPosition] = useState<{ left: number; top: number } | null>(null); + const dragStateRef = useRef<{ dragging: boolean; startX: number; startY: number; @@ -307,8 +309,8 @@ function Window({ title, onClose, children, headerActions }: WindowProps) { } | null>(null); // Track size when user resizes the window - const [size, setSize] = React.useState<{ width: number; height: number } | null>(null); - const resizeStateRef = React.useRef<{ + const [size, setSize] = useState<{ width: number; height: number } | null>(null); + const resizeStateRef = useRef<{ resizing: boolean; startX: number; startY: number; diff --git a/packages/utils/src/useCallback.ts b/packages/utils/src/useCallback.ts new file mode 100644 index 00000000000..353a512418f --- /dev/null +++ b/packages/utils/src/useCallback.ts @@ -0,0 +1,4 @@ +'use client'; +import { createUseCallback } from './fastHooks'; + +export const useCallback = createUseCallback(); diff --git a/packages/utils/src/useControlled.ts b/packages/utils/src/useControlled.ts index 904f1619385..1b0dc6d531b 100644 --- a/packages/utils/src/useControlled.ts +++ b/packages/utils/src/useControlled.ts @@ -2,6 +2,10 @@ // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- process.env never changes, dependency arrays are intentionally ignored /* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */ import * as React from 'react'; +import { useEffect } from './useEffect'; +import { useRef } from './useRef'; +import { useCallback } from './useCallback'; +import { useState } from './useState'; export interface UseControlledProps { /** @@ -29,12 +33,12 @@ export function useControlled({ state = 'value', }: UseControlledProps): [T, (newValue: T | ((prevValue: T) => T)) => void] { // isControlled is ignored in the hook dependency lists as it should never change. - const { current: isControlled } = React.useRef(controlled !== undefined); - const [valueState, setValue] = React.useState(defaultProp); + const { current: isControlled } = useRef(controlled !== undefined); + const [valueState, setValue] = useState(defaultProp); const value = isControlled ? controlled : valueState; if (process.env.NODE_ENV !== 'production') { - React.useEffect(() => { + useEffect(() => { if (isControlled !== (controlled !== undefined)) { console.error( [ @@ -51,9 +55,9 @@ export function useControlled({ } }, [state, name, controlled]); - const { current: defaultValue } = React.useRef(defaultProp); + const { current: defaultValue } = useRef(defaultProp); - React.useEffect(() => { + useEffect(() => { // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is for more details. if (!isControlled && JSON.stringify(defaultValue) !== JSON.stringify(defaultProp)) { console.error( @@ -66,7 +70,7 @@ export function useControlled({ }, [JSON.stringify(defaultProp)]); } - const setValueIfUncontrolled = React.useCallback((newValue: React.SetStateAction) => { + const setValueIfUncontrolled = useCallback((newValue: React.SetStateAction) => { if (!isControlled) { setValue(newValue as T); } diff --git a/packages/utils/src/useEffect.ts b/packages/utils/src/useEffect.ts new file mode 100644 index 00000000000..89c0eee7041 --- /dev/null +++ b/packages/utils/src/useEffect.ts @@ -0,0 +1,4 @@ +'use client'; +import { createUseEffect } from './fastHooks'; + +export const useEffect = createUseEffect('useEffect'); diff --git a/packages/utils/src/useEnhancedClickHandler.ts b/packages/utils/src/useEnhancedClickHandler.ts index a6957662d66..a91775c6214 100644 --- a/packages/utils/src/useEnhancedClickHandler.ts +++ b/packages/utils/src/useEnhancedClickHandler.ts @@ -1,4 +1,6 @@ import * as React from 'react'; +import { useRef } from './useRef'; +import { useCallback } from './useCallback'; export type InteractionType = 'mouse' | 'touch' | 'pen' | 'keyboard' | ''; @@ -12,9 +14,9 @@ export type InteractionType = 'mouse' | 'touch' | 'pen' | 'keyboard' | ''; export function useEnhancedClickHandler( handler: (event: React.MouseEvent | React.PointerEvent, interactionType: InteractionType) => void, ) { - const lastClickInteractionTypeRef = React.useRef(''); + const lastClickInteractionTypeRef = useRef(''); - const handlePointerDown = React.useCallback( + const handlePointerDown = useCallback( (event: React.PointerEvent) => { if (event.defaultPrevented) { return; @@ -26,7 +28,7 @@ export function useEnhancedClickHandler( [handler], ); - const handleClick = React.useCallback( + const handleClick = useCallback( (event: React.MouseEvent | React.PointerEvent) => { // event.detail has the number of clicks performed on the element. 0 means it was triggered by the keyboard. if (event.detail === 0) { diff --git a/packages/utils/src/useForcedRerendering.ts b/packages/utils/src/useForcedRerendering.ts index 0c2d573eaaf..ed3a4dbb0a9 100644 --- a/packages/utils/src/useForcedRerendering.ts +++ b/packages/utils/src/useForcedRerendering.ts @@ -1,13 +1,15 @@ 'use client'; import * as React from 'react'; +import { useCallback } from './useCallback'; +import { useState } from './useState'; /** * Returns a function that forces a rerender. */ export function useForcedRerendering() { - const [, setState] = React.useState({}); + const [, setState] = useState({}); - return React.useCallback(() => { + return useCallback(() => { setState({}); }, []); } diff --git a/packages/utils/src/useId.ts b/packages/utils/src/useId.ts index 2d2cf04dbe9..7bc3d70cdd8 100644 --- a/packages/utils/src/useId.ts +++ b/packages/utils/src/useId.ts @@ -1,14 +1,16 @@ 'use client'; import * as React from 'react'; import { SafeReact } from './safeReact'; +import { useEffect } from './useEffect'; +import { useState } from './useState'; let globalId = 0; // TODO React 17: Remove `useGlobalId` once React 17 support is removed function useGlobalId(idOverride?: string, prefix: string = 'mui'): string | undefined { - const [defaultId, setDefaultId] = React.useState(idOverride); + const [defaultId, setDefaultId] = useState(idOverride); const id = idOverride || defaultId; - React.useEffect(() => { + useEffect(() => { if (defaultId == null) { // Fallback to this default id when possible. // Use the incrementing value for client-side rendering only. diff --git a/packages/utils/src/useInsertionEffect.ts b/packages/utils/src/useInsertionEffect.ts new file mode 100644 index 00000000000..e403c6d5a28 --- /dev/null +++ b/packages/utils/src/useInsertionEffect.ts @@ -0,0 +1,4 @@ +'use client'; +import { createUseEffect } from './fastHooks'; + +export const useInsertionEffect = createUseEffect('useInsertionEffect'); diff --git a/packages/utils/src/useIsoLayoutEffect.ts b/packages/utils/src/useIsoLayoutEffect.ts index 01d3a9e9bf4..de756186eca 100644 --- a/packages/utils/src/useIsoLayoutEffect.ts +++ b/packages/utils/src/useIsoLayoutEffect.ts @@ -1,6 +1,7 @@ 'use client'; -import * as React from 'react'; +import { createUseEffect } from './fastHooks'; const noop = () => {}; -export const useIsoLayoutEffect = typeof document !== 'undefined' ? React.useLayoutEffect : noop; +export const useIsoLayoutEffect = + typeof document !== 'undefined' ? createUseEffect('useLayoutEffect') : noop; diff --git a/packages/utils/src/useOnFirstRender.ts b/packages/utils/src/useOnFirstRender.ts index 23aa934dbdd..4a05296b208 100644 --- a/packages/utils/src/useOnFirstRender.ts +++ b/packages/utils/src/useOnFirstRender.ts @@ -1,7 +1,8 @@ import * as React from 'react'; +import { useRef } from './useRef'; export function useOnFirstRender(fn: Function) { - const ref = React.useRef(true); + const ref = useRef(true); if (ref.current) { ref.current = false; fn(); diff --git a/packages/utils/src/useOnMount.ts b/packages/utils/src/useOnMount.ts index 65ce7a019a4..e9fd4dd62e3 100644 --- a/packages/utils/src/useOnMount.ts +++ b/packages/utils/src/useOnMount.ts @@ -1,6 +1,8 @@ 'use client'; import * as React from 'react'; +import { useEffect } from './useEffect'; + const EMPTY = [] as unknown[]; /** @@ -9,6 +11,6 @@ const EMPTY = [] as unknown[]; export function useOnMount(fn: React.EffectCallback) { // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- no need to put `fn` in the dependency array /* eslint-disable react-hooks/exhaustive-deps */ - React.useEffect(fn, EMPTY); + useEffect(fn, EMPTY); /* eslint-enable react-hooks/exhaustive-deps */ } diff --git a/packages/utils/src/usePreviousValue.ts b/packages/utils/src/usePreviousValue.ts index 1a21a7395d2..5ccd17a2172 100644 --- a/packages/utils/src/usePreviousValue.ts +++ b/packages/utils/src/usePreviousValue.ts @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import { useState } from './useState'; /** * Returns a previous value of its argument. @@ -7,7 +8,7 @@ import * as React from 'react'; * @returns Previous value, or null if there is no previous value. */ export function usePreviousValue(value: T): T | null { - const [state, setState] = React.useState<{ current: T; previous: T | null }>({ + const [state, setState] = useState<{ current: T; previous: T | null }>({ current: value, previous: null, }); diff --git a/packages/utils/src/useRef.ts b/packages/utils/src/useRef.ts new file mode 100644 index 00000000000..b4bf6609e8a --- /dev/null +++ b/packages/utils/src/useRef.ts @@ -0,0 +1,4 @@ +'use client'; +import { createUseRef } from './fastHooks'; + +export const useRef = createUseRef(); diff --git a/packages/utils/src/useRefWithInit.ts b/packages/utils/src/useRefWithInit.ts index 6c13a09648e..8d22f062c5a 100644 --- a/packages/utils/src/useRefWithInit.ts +++ b/packages/utils/src/useRefWithInit.ts @@ -1,10 +1,10 @@ 'use client'; -import * as React from 'react'; +import { useRef } from './useRef'; const UNINITIALIZED = {}; /** - * A React.useRef() that is initialized with a function. Note that it accepts an optional + * A useRef() that is initialized with a function. Note that it accepts an optional * initialization argument, so the initialization function doesn't need to be an inline closure. * * @usage @@ -13,7 +13,9 @@ const UNINITIALIZED = {}; export function useRefWithInit(init: () => T): React.RefObject; export function useRefWithInit(init: (arg: U) => T, initArg: U): React.RefObject; export function useRefWithInit(init: (arg?: unknown) => unknown, initArg?: unknown) { - const ref = React.useRef(UNINITIALIZED as any); + // Note: We use React.useRef directly here to avoid circular dependencies + // since fastHooks.ts depends on useRefWithInit + const ref = useRef(UNINITIALIZED as any); if (ref.current === UNINITIALIZED) { ref.current = init(initArg); diff --git a/packages/utils/src/useState.ts b/packages/utils/src/useState.ts new file mode 100644 index 00000000000..1666986183f --- /dev/null +++ b/packages/utils/src/useState.ts @@ -0,0 +1,4 @@ +'use client'; +import { createUseState } from './fastHooks'; + +export const useState = createUseState();