Skip to content

PopoverTarget: mergeRefs not memoized, causes infinite re-render loop #7857

@kajtzu

Description

@kajtzu

Bug description

In PopoverTarget (packages/core/src/components/popover-next/popoverTarget.tsx), mergeRefs is called unconditionally in the render body of a functional component:

const ref = mergeRefs(floatingData.refs.setReference, targetRef);

This produces a new ref callback on every render. When React sees a changed ref callback, it invokes the previous one with null and the new one with the DOM node. The setReference(null) triggers a floating-ui state update, which causes a re-render, which creates another new mergeRefs result — producing an infinite loop that crashes with:

Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.

Notably, Blueprint's own JSDoc on mergeRefs already warns about this:

/**
 * Utility for merging refs into one singular callback ref.
 * If using in a functional component, would recomend using `useMemo` to preserve function identity.
 */
export function mergeRefs<T>(...refs: Array<React.Ref<T> | undefined>): React.RefCallback<T> {

PopoverTarget is a forwardRef functional component but does not follow this guidance.

Steps to reproduce

  1. Render a PopoverNext whose parent re-renders frequently (e.g. connected to a Redux store or React Query cache that resolves synchronously on mount)
  2. The page crashes immediately with a stack trace through floating-ui.react.mjs → floating-ui setReferencePopoverTarget

The crash is timing-dependent — it requires the parent to re-render fast enough that React's ref cleanup/re-assignment cycle doesn't settle. Synchronous cache hits (React Query, Redux selectors) reliably trigger it.

Suggested fix

Memoize the mergeRefs call in popoverTarget.tsx:

const ref = useMemo(() => mergeRefs(floatingData.refs.setReference, targetRef), [floatingData.refs.setReference, targetRef]);

Environment

  • @blueprintjs/core 6.9.1 (also confirmed on develop branch HEAD)
  • React 18.3.0
  • @floating-ui/react (as bundled/re-exported by Blueprint)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions