Skip to content

update eslint-plugin-react-hooks to latest#7771

Merged
mattcosta7 merged 5 commits intomainfrom
eslint-plugin-react-hooks-update
Apr 22, 2026
Merged

update eslint-plugin-react-hooks to latest#7771
mattcosta7 merged 5 commits intomainfrom
eslint-plugin-react-hooks-update

Conversation

@mattcosta7
Copy link
Copy Markdown
Contributor

@mattcosta7 mattcosta7 commented Apr 22, 2026

Closes #

Updates eslint-plugin-react-hooks to the latest version
Removes the deprecated eslint-plugin-react-compiler - the rules were incorporated into react-hooks directly.

Adds new inline disables for error/warnings generated by the current version of the eslint rules.

Changelog

New

Changed

Removed

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

No code changes, just eslint warning disables

Testing & Reviewing

Merge checklist

@mattcosta7 mattcosta7 self-assigned this Apr 22, 2026
Copilot AI review requested due to automatic review settings April 22, 2026 14:10
@mattcosta7 mattcosta7 requested a review from a team as a code owner April 22, 2026 14:10
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 22, 2026

⚠️ No Changeset found

Latest commit: 439630a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions github-actions Bot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label Apr 22, 2026
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Action required

👋 Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the integration workflow. Check the integration testing docs for step-by-step instructions. Or, apply the integration-tests: skipped manually label to skip these checks.

To publish a canary release for integration testing, apply the Canary Release label to this PR.

@mattcosta7 mattcosta7 added skip changeset This change does not need a changelog integration-tests: skipped manually Changes in this PR do not require an integration test and removed integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm labels Apr 22, 2026
@mattcosta7 mattcosta7 changed the title updates update eslint-plugin-react-hooks to latest Apr 22, 2026
event.preventDefault()
setOpen(true)
}
const ListItemWithContextMenu = ({children}: {children: string}) => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the eslint plugin will complain about a function component created inside a component - we can extract to module scope to fix

Comment thread packages/react/src/stories/deprecated/ActionList.stories.tsx Outdated
Comment thread packages/react/src/Portal/Portal.features.stories.tsx Outdated
Comment thread packages/react/src/NavList/NavList.test.tsx Outdated
Comment thread packages/react/src/NavList/NavList.features.stories.tsx Outdated
Comment thread packages/react/src/deprecated/utils/create-slots.tsx Outdated
Comment thread packages/react/src/Autocomplete/Autocomplete.features.stories.tsx Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the repo’s ESLint setup and code annotations to accommodate newer React Hooks linting behavior, while removing the React Compiler ESLint plugin.

Changes:

  • Remove eslint-plugin-react-compiler and its ESLint rule configuration; update eslint-plugin-react-hooks to ^7.1.1.
  • Add/adjust eslint-disable-next-line directives across components/hooks/stories to address newly enforced hooks rules.
  • Minor refactors/formatting in examples (e.g., ActionMenu context menu helper extraction).
Show a summary per file
File Description
packages/react/src/utils/StressTest.tsx Adds targeted ESLint disables for new hooks lint rules.
packages/react/src/stories/deprecated/ActionList.stories.tsx Removes an eslint-disable directive that’s no longer needed.
packages/react/src/hooks/useOnEscapePress.ts Expands eslint-disable to include a new hooks rule.
packages/react/src/hooks/useFocusTrap.ts Adds eslint-disable directives around ref usage and mutation patterns.
packages/react/src/hooks/useAnchoredPosition.ts Expands eslint-disable to include a new hooks rule.
packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx Adds eslint-disable for set-state-in-effect rule.
packages/react/src/experimental/SelectPanel2/SelectPanel.tsx Adds eslint-disable directives for refs and set-state-in-effect.
packages/react/src/experimental/SelectPanel2/SelectPanel.examples.stories.tsx Adds eslint-disable for set-state-in-effect in examples.
packages/react/src/deprecated/utils/create-slots.tsx Removes an eslint-disable directive that’s no longer needed.
packages/react/src/TreeView/TreeView.tsx Adds eslint-disable for set-state-in-effect.
packages/react/src/TooltipV2/Tooltip.tsx Removes an eslint-disable directive that’s no longer needed.
packages/react/src/SelectPanel/SelectPanel.tsx Adds eslint-disable directives for set-state-in-effect.
packages/react/src/SelectPanel/SelectPanel.features.stories.tsx Adds eslint-disable directives for set-state-in-effect in stories.
packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx Adds eslint-disable directives for set-state-in-effect and incompatible-library.
packages/react/src/Portal/Portal.tsx Adds eslint-disable directives for refs usage.
packages/react/src/Portal/Portal.features.stories.tsx Removes an eslint-disable directive that’s no longer needed.
packages/react/src/NavList/NavList.test.tsx Removes an eslint-disable directive that’s no longer needed.
packages/react/src/NavList/NavList.features.stories.tsx Removes an eslint-disable directive that’s no longer needed.
packages/react/src/LabelGroup/LabelGroup.tsx Adds eslint-disable directives for refs/immutability/set-state-in-effect.
packages/react/src/FormControl/FormControl.features.stories.tsx Adds eslint-disable directives for set-state-in-effect.
packages/react/src/FilteredActionList/FilteredActionList.tsx Adds eslint-disable for incompatible-library rule.
packages/react/src/Dialog/Dialog.tsx Adds eslint-disable directives for immutability/refs/set-state-in-effect.
packages/react/src/DataTable/storybook/data.ts Adds eslint-disable for set-state-in-effect.
packages/react/src/Autocomplete/Autocomplete.features.stories.tsx Removes an eslint-disable directive that’s no longer needed.
packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx Adds eslint-disable directives for refs usage in several places.
packages/react/src/ActionMenu/ActionMenu.examples.stories.tsx Refactors context-menu helper component placement/structure.
packages/react/src/ActionList/ActionList.examples.stories.tsx Removes an eslint-disable directive that’s no longer needed.
package.json Removes eslint-plugin-react-compiler, updates eslint-plugin-react-hooks.
package-lock.json Lockfile updates reflecting dependency/plugin/version changes.
eslint.config.mjs Removes react-compiler plugin registration; retains hooks config and other lint config.

Copilot's findings

Comments suppressed due to low confidence (3)

packages/react/src/LabelGroup/LabelGroup.tsx:160

  • expandButtonRef is a callback ref but is being treated like a mutable ref object (expandButtonRef.current = node) and later cast to RefObject for anchorRef. This relies on a non-standard property on a function plus @ts-ignore, and can break if React changes ref handling. Prefer using a separate useRef<HTMLButtonElement | null>() to hold the node, and a callback that writes into that ref (and optionally forwards to any external ref) so you can pass a real RefObject to AnchoredOverlay.
  const expandButtonRef: React.RefCallback<HTMLButtonElement> = React.useCallback(
    // eslint-disable-next-line react-hooks/immutability
    node => {
      if (node !== null) {
        const nodeClientRect = node.getBoundingClientRect()

        if (nodeClientRect.width !== buttonClientRect.width || nodeClientRect.right !== buttonClientRect.right) {
          setButtonClientRect(nodeClientRect)
        }

        // @ts-ignore you can set `.current` on ref objects or ref callbacks in React
        // eslint-disable-next-line react-hooks/immutability
        expandButtonRef.current = node
      }

eslint.config.mjs:70

  • eslint.config.mjs still configures the react-compiler/react-compiler rule (set to off) even though eslint-plugin-react-compiler has been removed from dependencies and is no longer registered in the config. ESLint will error with “Definition for rule 'react-compiler/react-compiler' was not found”. Remove this rule override block (and the now-unused reactCompilerUnsupported import) or re-add/register the plugin.
  reactHooks.configs.flat['recommended-latest'],
  // Disable react-compiler rule for files not yet migrated
  {
    files: reactCompilerUnsupported.map(p => `packages/react/${p}`),
    rules: {
      'react-compiler/react-compiler': 'off',
    },
  },

packages/react/src/hooks/useFocusTrap.ts:109

  • onClickOutside mutates the caller-provided settings object (settings.returnFocusRef = undefined, settings.restoreFocusOnCleanUp = false). Mutating input objects makes this hook’s behavior surprising for callers and can cause hard-to-debug side effects if the same settings object is reused elsewhere. Prefer keeping these overrides in hook-local state/refs (e.g., a skipRestoreFocusRef) and have disableTrap() consult that instead of mutating settings.
      if (settings?.allowOutsideClick) {
        // eslint-disable-next-line react-hooks/immutability
        if (settings.returnFocusRef) settings.returnFocusRef = undefined
        settings.restoreFocusOnCleanUp = false
        abortController.current?.abort()
      }
  • Files reviewed: 29/30 changed files
  • Comments generated: 3

Comment on lines 166 to 170
const cssAnchorPositioningFlag = useFeatureFlag('primer_react_css_anchor_positioning')
const supportsNativeCSSAnchorPositioning = useRef(false)
// eslint-disable-next-line react-hooks/refs
const cssAnchorPositioning = cssAnchorPositioningFlag && supportsNativeCSSAnchorPositioning.current
const anchorRef = useProvidedRefOrCreate(externalAnchorRef)
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

supportsNativeCSSAnchorPositioning is stored in a ref and updated in an effect, but cssAnchorPositioning is derived from supportsNativeCSSAnchorPositioning.current during render. If this component mounts with open={true}, the effect update won’t trigger a rerender, so cssAnchorPositioning can remain false until some unrelated rerender happens. Consider storing support detection in state (set in an effect) or computing it lazily in a way that guarantees a rerender when the value changes.

Copilot uses AI. Check for mistakes.
Comment on lines 62 to 67
// If we are enabling a focus trap and haven't already stored the previously focused element
// go ahead an do that so we can restore later when the trap is disabled.
// eslint-disable-next-line react-hooks/refs
if (!previousFocusedElement.current && !disabled) {
previousFocusedElement.current = document.activeElement
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

This hook reads document.activeElement during render to populate previousFocusedElement.current. Accessing document in render will throw during SSR and is also unsafe under concurrent rendering/StrictMode (render can run more than once). Capture activeElement inside an effect/layout effect that runs when the trap is enabled, with a typeof document !== 'undefined' guard.

Suggested change
// If we are enabling a focus trap and haven't already stored the previously focused element
// go ahead an do that so we can restore later when the trap is disabled.
// eslint-disable-next-line react-hooks/refs
if (!previousFocusedElement.current && !disabled) {
previousFocusedElement.current = document.activeElement
}
// Store the currently focused element when the trap is enabled so focus can be restored
// after the trap is cleaned up.
React.useLayoutEffect(() => {
if (!disabled && !previousFocusedElement.current && typeof document !== 'undefined') {
previousFocusedElement.current = document.activeElement
}
}, [disabled])

Copilot uses AI. Check for mistakes.
Comment on lines 279 to 283
for (const footerButton of footerButtons) {
if (footerButton.autoFocus) {
// eslint-disable-next-line react-hooks/immutability
footerButton.ref = autoFocusedFooterButtonRef
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

This loop mutates footerButtons items (footerButton.ref = ...) during render. Since footerButtons comes from props, this can leak mutations back to callers and can also break memoization assumptions. Prefer deriving a new buttons array (e.g., via map) with the ref injected for the autoFocus button, or change Dialog.Buttons to accept the autoFocusedFooterButtonRef separately so no prop mutation is needed.

See below for a potential fix:

  const resolvedFooterButtons = footerButtons.map(footerButton =>
    footerButton.autoFocus ? {...footerButton, ref: autoFocusedFooterButtonRef} : footerButton,
  )
  const [lastMouseDownIsBackdrop, setLastMouseDownIsBackdrop] = useState<boolean>(false)
  const [footerButtonLayout, setFooterButtonLayout] = useState<'scroll' | 'wrap'>('wrap')
  const defaultedProps = {
    ...props,
    title,
    subtitle,
    role,
    dialogLabelId,
    dialogDescriptionId,
    footerButtons: resolvedFooterButtons,
  }

Copilot uses AI. Check for mistakes.
Co-authored-by: Matthew Costabile <mattcosta7@github.com>
@github-actions github-actions Bot temporarily deployed to storybook-preview-7771 April 22, 2026 15:12 Inactive
@TylerJDev TylerJDev added the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Apr 22, 2026
@github-actions github-actions Bot removed the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Apr 22, 2026
@github-actions github-actions Bot temporarily deployed to storybook-preview-7771 April 22, 2026 16:57 Inactive
@github-actions github-actions Bot temporarily deployed to storybook-preview-7771 April 22, 2026 17:06 Inactive
@github-actions github-actions Bot temporarily deployed to storybook-preview-7771 April 22, 2026 17:17 Inactive
@TylerJDev TylerJDev added the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Apr 22, 2026
@github-actions github-actions Bot removed the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Apr 22, 2026
@github-actions github-actions Bot temporarily deployed to storybook-preview-7771 April 22, 2026 17:31 Inactive
@github-actions github-actions Bot temporarily deployed to storybook-preview-7771 April 22, 2026 17:43 Inactive
@mattcosta7 mattcosta7 added this pull request to the merge queue Apr 22, 2026
Merged via the queue into main with commit 29dc62f Apr 22, 2026
53 checks passed
@mattcosta7 mattcosta7 deleted the eslint-plugin-react-hooks-update branch April 22, 2026 18:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration-tests: skipped manually Changes in this PR do not require an integration test skip changeset This change does not need a changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants