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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions