Skip to content

[core] Remove useComponentRenderer #2055

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { useModernLayoutEffect } from '../../utils/useModernLayoutEffect';
import { useBaseUiId } from '../../utils/useBaseUiId';
import type { BaseUIComponentProps } from '../../utils/types';

const state = {};

/**
* A paragraph with additional information about the alert dialog.
* Renders a `<p>` element.
Expand All @@ -31,9 +29,8 @@ export const AlertDialogDescription = React.forwardRef(function AlertDialogDescr
}, [id, setDescriptionElementId]);

return useRenderElement('p', componentProps, {
state,
ref: forwardedRef,
props: [elementProps, { id }],
props: [{ id }, elementProps],
Copy link
Member

Choose a reason for hiding this comment

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

This actually doesn't matter as elementProps can't contain id.

Copy link
Contributor

@romgrk romgrk Jun 4, 2025

Choose a reason for hiding this comment

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

Sidenote on perf, I've been looking into having uRE automatically handle merging componentProps minus render & className into props, it would avoid destructuring into elementProps for some components (those that don't extract more props out of componentProps). The only thing stopping me right now is that some components don't have elementProps as the final element in the props array.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@michaldudak yeah, but each component was doing something different - just changed to be the most consistent

});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { useModernLayoutEffect } from '../../utils/useModernLayoutEffect';
import { useBaseUiId } from '../../utils/useBaseUiId';
import type { BaseUIComponentProps } from '../../utils/types';

const state = {};

/**
* A heading that labels the dialog.
* Renders an `<h2>` element.
Expand All @@ -31,9 +29,8 @@ export const AlertDialogTitle = React.forwardRef(function AlertDialogTitle(
}, [id, setTitleElementId]);

return useRenderElement('h2', componentProps, {
state,
ref: forwardedRef,
props: [elementProps, { id }],
props: [{ id }, elementProps],
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { useModernLayoutEffect } from '../../utils/useModernLayoutEffect';
import { useBaseUiId } from '../../utils/useBaseUiId';
import type { BaseUIComponentProps } from '../../utils/types';

const state = {};

/**
* A paragraph with additional information about the dialog.
* Renders a `<p>` element.
Expand All @@ -31,9 +29,8 @@ export const DialogDescription = React.forwardRef(function DialogDescription(
}, [id, setDescriptionElementId]);

return useRenderElement('p', componentProps, {
state,
ref: forwardedRef,
props: [elementProps, { id }],
props: [{ id }, elementProps],
});
});

Expand Down
5 changes: 1 addition & 4 deletions packages/react/src/dialog/title/DialogTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { useModernLayoutEffect } from '../../utils/useModernLayoutEffect';
import { useBaseUiId } from '../../utils/useBaseUiId';
import { type BaseUIComponentProps } from '../../utils/types';

const state = {};

/**
* A heading that labels the dialog.
* Renders an `<h2>` element.
Expand All @@ -31,9 +29,8 @@ export const DialogTitle = React.forwardRef(function DialogTitle(
}, [id, setTitleElementId]);

return useRenderElement('h2', componentProps, {
state,
ref: forwardedRef,
props: [elementProps, { id }],
props: [{ id }, elementProps],
});
});

Expand Down
3 changes: 0 additions & 3 deletions packages/react/src/menu/group-label/MenuGroupLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { useBaseUiId } from '../../utils/useBaseUiId';
import { useMenuGroupRootContext } from '../group/MenuGroupContext';
import { useModernLayoutEffect } from '../../utils/useModernLayoutEffect';

const state = {};

/**
* An accessible label that is automatically associated with its parent group.
* Renders a `<div>` element.
Expand All @@ -32,7 +30,6 @@ export const MenuGroupLabel = React.forwardRef(function MenuGroupLabelComponent(
}, [setLabelId, id]);

return useRenderElement('div', componentProps, {
state,
ref: forwardedRef,
props: {
id,
Expand Down
3 changes: 0 additions & 3 deletions packages/react/src/menu/group/MenuGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { BaseUIComponentProps } from '../../utils/types';
import { useRenderElement } from '../../utils/useRenderElement';
import { MenuGroupContext } from './MenuGroupContext';

const state = {};

/**
* Groups related menu items with the corresponding label.
* Renders a `<div>` element.
Expand All @@ -23,7 +21,6 @@ export const MenuGroup = React.forwardRef(function MenuGroup(
const context = React.useMemo(() => ({ setLabelId }), [setLabelId]);

const element = useRenderElement('div', componentProps, {
state,
ref: forwardedRef,
props: {
role: 'group',
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/meter/label/MeterLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const MeterLabel = React.forwardRef(function MeterLabel(

return useRenderElement('span', componentProps, {
ref: forwardedRef,
props: [elementProps, { id }],
props: [{ id }, elementProps],
});
});

Expand Down
5 changes: 2 additions & 3 deletions packages/react/src/utils/floating-ui/useClick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import * as React from 'react';
import type { ElementProps, FloatingRootContext } from '@floating-ui/react';
import { isMouseLikePointerType } from '@floating-ui/react/utils';
import { useAnimationFrame } from '../useAnimationFrame';

const EMPTY_OBJECT = {};
import { EMPTY_OBJ } from '../constants';

export interface UseClickProps {
/**
Expand Down Expand Up @@ -118,5 +117,5 @@ export function useClick(context: FloatingRootContext, props: UseClickProps = {}
[dataRef, eventOption, ignoreMouse, onOpenChange, open, stickIfOpen, toggle, frame],
);

return React.useMemo(() => (enabled ? { reference } : EMPTY_OBJECT), [enabled, reference]);
return React.useMemo(() => (enabled ? { reference } : EMPTY_OBJ), [enabled, reference]);
}
33 changes: 0 additions & 33 deletions packages/react/src/utils/useComponentRenderer.test.tsx

This file was deleted.

68 changes: 0 additions & 68 deletions packages/react/src/utils/useComponentRenderer.ts

This file was deleted.

41 changes: 7 additions & 34 deletions packages/react/src/utils/useRenderElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import { resolveClassName } from './resolveClassName';
import { isReactVersionAtLeast } from './reactVersion';
import { mergeProps, mergePropsN } from '../merge-props';
import { mergeObjects } from './mergeObjects';
import { EMPTY_OBJ } from './constants';

type IntrinsicTagName = keyof React.JSX.IntrinsicElements;

const EMPTY_OBJECT = {};
const IDENTITY = (x: any) => x;

/**
* Renders a Base UI element.
*
Expand All @@ -35,34 +33,12 @@ export function useRenderElement<
return null as Enabled extends false ? null : React.ReactElement<Record<string, unknown>>;
}

const state = params.state ?? (EMPTY_OBJECT as State);
const state = params.state ?? (EMPTY_OBJ as State);
return evaluateRenderProp(element, renderProp, outProps, state) as Enabled extends false
? null
: React.ReactElement<Record<string, unknown>>;
}

/**
* Returns a function that renders a Base UI element.
*
* @deprecated Use `useRenderElement` instead and pass `enabled = false` to its options instead.
*/
// TODO: Remove once useComponentRenderer is no longer used.
export function useRenderElementLazy<
State extends Record<string, any>,
RenderedElementType extends Element,
TagName extends IntrinsicTagName | undefined,
Enabled extends boolean | undefined,
>(
element: TagName,
componentProps: useRenderElement.ComponentProps<State>,
params: useRenderElement.Parameters<State, RenderedElementType, TagName, Enabled> = {},
) {
const renderProp = componentProps.render;
const outProps = useRenderElementProps(componentProps, params);
const state = params.state ?? (EMPTY_OBJECT as State);
return () => evaluateRenderProp(element, renderProp, outProps, state);
}

/**
* Computes render element final props.
*/
Expand All @@ -78,8 +54,7 @@ function useRenderElementProps<
const { className: classNameProp, render: renderProp } = componentProps;

const {
propGetter = IDENTITY,
state = EMPTY_OBJECT as State,
state = EMPTY_OBJ as State,
ref,
props,
disableStyleHooks,
Expand All @@ -95,16 +70,14 @@ function useRenderElementProps<
// always unset, so this `if` block is stable across renders.
/* eslint-disable-next-line react-hooks/rules-of-hooks */
styleHooks = React.useMemo(
() => (enabled ? getStyleHookProps(state, customStyleHookMapping) : EMPTY_OBJECT),
() => (enabled ? getStyleHookProps(state, customStyleHookMapping) : EMPTY_OBJ),
[state, customStyleHookMapping, enabled],
);
}

const outProps: React.HTMLAttributes<any> & React.RefAttributes<any> = enabled
? propGetter(
mergeObjects(styleHooks, Array.isArray(props) ? mergePropsN(props) : props) ?? EMPTY_OBJECT,
)
: EMPTY_OBJECT;
? (mergeObjects(styleHooks, Array.isArray(props) ? mergePropsN(props) : props) ?? EMPTY_OBJ)
: EMPTY_OBJ;

// SAFETY: The `useForkRef` functions use a single hook to store the same value,
// switching between them at runtime is safe. If this assertion fails, React will
Expand All @@ -120,7 +93,7 @@ function useRenderElementProps<
/* eslint-enable react-hooks/rules-of-hooks */

if (!enabled) {
return EMPTY_OBJECT;
return EMPTY_OBJ;
}

if (className !== undefined) {
Expand Down