Skip to content

Commit

Permalink
fix: save render through optimized useSlot() (#7270)
Browse files Browse the repository at this point in the history
* allow for initializing slot with a false state

* save slot render in checkboxgroup

* save slot render in radiogroup

* save slot render in meter

* save render in select

* save render in timefield

* save render in datefield

* save render in hexcolorfield

* save render in colorchannelfield

* save render in progressbar

* saver render in combobox

* save render in searchfield

* save render in numberfield

* save render in taggroup

* save render in textfield

* save render in colorslider

* save render in slider

* save render in datepicker

* Update packages/react-aria-components/src/utils.tsx

---------

Co-authored-by: Robert Snow <[email protected]>
  • Loading branch information
nwidynski and snowystinger authored Feb 3, 2025
1 parent 4b641a7 commit cfcd697
Show file tree
Hide file tree
Showing 16 changed files with 61 additions and 22 deletions.
4 changes: 3 additions & 1 deletion packages/react-aria-components/src/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ export const CheckboxGroup = /*#__PURE__*/ (forwardRef as forwardRefType)(functi
...props,
validationBehavior
});
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {groupProps, labelProps, descriptionProps, errorMessageProps, ...validation} = useCheckboxGroup({
...props,
label,
Expand Down
8 changes: 6 additions & 2 deletions packages/react-aria-components/src/ColorField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ function ColorChannelField(props: ColorChannelFieldProps) {
});

let inputRef = useRef<HTMLInputElement>(null);
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {
labelProps,
inputProps,
Expand Down Expand Up @@ -129,7 +131,9 @@ function HexColorField(props: HexColorFieldProps) {
});

let inputRef = useRef<HTMLInputElement>(null);
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {
labelProps,
inputProps,
Expand Down
4 changes: 3 additions & 1 deletion packages/react-aria-components/src/ColorSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ export const ColorSlider = forwardRef(function ColorSlider(props: ColorSliderPro
let trackRef = React.useRef(null);
let inputRef = React.useRef(null);

let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {
trackProps,
thumbProps,
Expand Down
4 changes: 3 additions & 1 deletion packages/react-aria-components/src/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ function ComboBoxInner<T extends object>({props, collection, comboBoxRef: ref}:
let inputRef = useRef<HTMLInputElement>(null);
let listBoxRef = useRef<HTMLDivElement>(null);
let popoverRef = useRef<HTMLDivElement>(null);
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {
buttonProps,
inputProps,
Expand Down
8 changes: 6 additions & 2 deletions packages/react-aria-components/src/DateField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ export const DateField = /*#__PURE__*/ (forwardRef as forwardRefType)(function D
});

let fieldRef = useRef<HTMLDivElement>(null);
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let inputRef = useRef<HTMLInputElement>(null);
let {labelProps, fieldProps, inputProps, descriptionProps, errorMessageProps, ...validation} = useDateField({
...removeDataAttributes(props),
Expand Down Expand Up @@ -127,7 +129,9 @@ export const TimeField = /*#__PURE__*/ (forwardRef as forwardRefType)(function T
});

let fieldRef = useRef<HTMLDivElement>(null);
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let inputRef = useRef<HTMLInputElement>(null);
let {labelProps, fieldProps, inputProps, descriptionProps, errorMessageProps, ...validation} = useTimeField({
...removeDataAttributes(props),
Expand Down
8 changes: 6 additions & 2 deletions packages/react-aria-components/src/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ export const DatePicker = /*#__PURE__*/ (forwardRef as forwardRefType)(function
});

let groupRef = useRef<HTMLDivElement>(null);
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {
groupProps,
labelProps,
Expand Down Expand Up @@ -186,7 +188,9 @@ export const DateRangePicker = /*#__PURE__*/ (forwardRef as forwardRefType)(func
});

let groupRef = useRef<HTMLDivElement>(null);
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {
groupProps,
labelProps,
Expand Down
4 changes: 3 additions & 1 deletion packages/react-aria-components/src/Meter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export const Meter = /*#__PURE__*/ (forwardRef as forwardRefType)(function Meter
} = props;
value = clamp(value, minValue, maxValue);

let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {
meterProps,
labelProps
Expand Down
4 changes: 3 additions & 1 deletion packages/react-aria-components/src/NumberField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ export const NumberField = /*#__PURE__*/ (forwardRef as forwardRefType)(function
});

let inputRef = useRef<HTMLInputElement>(null);
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {
labelProps,
groupProps,
Expand Down
4 changes: 3 additions & 1 deletion packages/react-aria-components/src/ProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ export const ProgressBar = forwardRef(function ProgressBar(props: ProgressBarPro
} = props;
value = clamp(value, minValue, maxValue);

let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {
progressBarProps,
labelProps
Expand Down
4 changes: 3 additions & 1 deletion packages/react-aria-components/src/RadioGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ export const RadioGroup = /*#__PURE__*/ (forwardRef as forwardRefType)(function
validationBehavior
});

let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {radioGroupProps, labelProps, descriptionProps, errorMessageProps, ...validation} = useRadioGroup({
...props,
label,
Expand Down
4 changes: 3 additions & 1 deletion packages/react-aria-components/src/SearchField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ export const SearchField = /*#__PURE__*/ (forwardRef as forwardRefType)(function
let validationBehavior = props.validationBehavior ?? formValidationBehavior ?? 'native';
let inputRef = useRef<HTMLInputElement>(null);
let [inputContextProps, mergedInputRef] = useContextProps({}, inputRef, InputContext);
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let state = useSearchFieldState({
...props,
validationBehavior
Expand Down
4 changes: 3 additions & 1 deletion packages/react-aria-components/src/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ function SelectInner<T extends object>({props, selectRef: ref, collection}: Sele

// Get props for child elements from useSelect
let buttonRef = useRef<HTMLButtonElement>(null);
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {
labelProps,
triggerProps,
Expand Down
8 changes: 6 additions & 2 deletions packages/react-aria-components/src/Slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ export const Slider = /*#__PURE__*/ (forwardRef as forwardRefType)(function Slid
let trackRef = useRef<HTMLDivElement>(null);
let numberFormatter = useNumberFormatter(props.formatOptions);
let state = useSliderState({...props, numberFormatter});
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {
groupProps,
trackProps,
Expand Down Expand Up @@ -221,7 +223,9 @@ export const SliderThumb = /*#__PURE__*/ (forwardRef as forwardRefType)(function
let {index = 0} = props;
let defaultInputRef = useRef<HTMLInputElement>(null);
let inputRef = userInputRef || defaultInputRef;
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let {thumbProps, inputProps, labelProps, isDragging, isFocused, isDisabled} = useSliderThumb({
...props,
index,
Expand Down
4 changes: 3 additions & 1 deletion packages/react-aria-components/src/TagGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ interface TagGroupInnerProps {

function TagGroupInner({props, forwardedRef: ref, collection}: TagGroupInnerProps) {
let tagListRef = useRef<HTMLDivElement>(null);
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let state = useListState({
...props,
children: undefined,
Expand Down
4 changes: 3 additions & 1 deletion packages/react-aria-components/src/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ export const TextField = /*#__PURE__*/ (forwardRef as forwardRefType)(function T
let validationBehavior = props.validationBehavior ?? formValidationBehavior ?? 'native';
let inputRef = useRef(null);
let [inputContextProps, mergedInputRef] = useContextProps({}, inputRef, InputContext);
let [labelRef, label] = useSlot();
let [labelRef, label] = useSlot(
!props['aria-label'] && !props['aria-labelledby']
);
let [inputElementType, setInputElementType] = useState('input');
let {labelProps, inputProps, descriptionProps, errorMessageProps, ...validation} = useTextField<any>({
...removeDataAttributes(props),
Expand Down
7 changes: 4 additions & 3 deletions packages/react-aria-components/src/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,10 @@ export function useContextProps<T, U extends SlotProps, E extends Element>(props
return [mergedProps, mergedRef];
}

export function useSlot(): [RefCallback<Element>, boolean] {
// Assume we do have the slot in the initial render.
let [hasSlot, setHasSlot] = useState(true);
export function useSlot(initialState: boolean | (() => boolean) = true): [RefCallback<Element>, boolean] {
// Initial state is typically based on the parent having an aria-label or aria-labelledby.
// If it does, this value should be false so that we don't update the state and cause a rerender when we go through the layoutEffect
let [hasSlot, setHasSlot] = useState(initialState);
let hasRun = useRef(false);

// A callback ref which will run when the slotted element mounts.
Expand Down

1 comment on commit cfcd697

@rspbot
Copy link

@rspbot rspbot commented on cfcd697 Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.