Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/react-compiler-button-base.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Button, IconButton, LinkButton: Improve rendering performance with React Compiler support.
1 change: 0 additions & 1 deletion packages/react/script/react-compiler.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const unsupportedPatterns = [
'src/ActionMenu/ActionMenu.tsx',
'src/AnchoredOverlay/AnchoredOverlay.tsx',
'src/Autocomplete/AutocompleteInput.tsx',
'src/Button/ButtonBase.tsx',
'src/Dialog/Dialog.tsx',
'src/FilteredActionList/FilteredActionList.tsx',
'src/LabelGroup/LabelGroup.tsx',
Expand Down
8 changes: 3 additions & 5 deletions packages/react/src/Button/ButtonBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Spinner from '../Spinner'
import CounterLabel from '../CounterLabel'
import {useId} from '../hooks'
import {ConditionalWrapper} from '../internal/components/ConditionalWrapper'
import {useDevOnlyEffect} from '../internal/hooks/useDevOnlyEffect'
import {AriaStatus} from '../live-region'
import {clsx} from 'clsx'
import classes from './ButtonBase.module.css'
Expand Down Expand Up @@ -75,15 +76,12 @@ const ButtonBase = forwardRef(({children, as: Component = 'button', ...props}, f
// Only include the loading aria-describedby if there is a loading state
const ariaDescribedByIds = loading ? [loadingAnnouncementID, ariaDescribedBy] : [ariaDescribedBy]

if (__DEV__) {
useDevOnlyEffect(() => {
if (notificationIndicator === 'leadingVisual' && !LeadingVisual) {
// eslint-disable-next-line no-console
console.warn('Button: `notificationIndicator="leadingVisual"` requires a `leadingVisual` prop.')
}

// Validate that the element is a semantic button/anchor.
// This runs during render (not in an effect) to avoid a conditional hook call
// that prevents React Compiler from optimizing this component.
const el = innerRef.current
if (
el &&
Expand All @@ -94,7 +92,7 @@ const ButtonBase = forwardRef(({children, as: Component = 'button', ...props}, f
// eslint-disable-next-line no-console
console.warn('This component should be an instanceof a semantic button or anchor')
}
}
})
return (
<ConditionalWrapper
// If anything is passed to `loading`, we need the wrapper:
Expand Down
12 changes: 12 additions & 0 deletions packages/react/src/Button/__tests__/Button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,18 @@ describe('Button', () => {
consoleSpy.mockRestore()
})

it('validates the rendered element after its ref is attached', () => {
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})

try {
render(<Button as="div">Invalid button</Button>)

expect(consoleSpy).toHaveBeenCalledWith('This component should be an instanceof a semantic button or anchor')
} finally {
consoleSpy.mockRestore()
}
})

it('respects the "disabled" prop', () => {
const onClick = vi.fn()
const container = render(
Expand Down
Loading