Skip to content

[BUG] AnimatePresence PopChild unconditionally reads children.props?.ref, triggering React 18.3 warning in non-popLayout mode #3745

@unional

Description

@unional

Bug description

PopChild reads children.props?.ref unconditionally on every render, regardless of whether mode=\"popLayout\" is active (i.e. whether pop is true). In React 18.3, creating a React element with a ref prop causes React to define a warning getter on element.props.ref via defineRefPropWarningGetter. When anything accesses that getter, it fires:

Warning: [object Object]: `ref` is not a prop. Trying to access it will result in
`undefined` being returned. If you need to access the same value within the child
component, you should pass it as a different prop.

The [object Object] display name occurs because motion.div (and other motion.* elements) are forwardRef objects — plain JS objects with $$typeof: REACT_FORWARD_REF_TYPE rather than functions — so typeof type !== 'function', and React's element validator sets displayName = type (the raw object), which String()-ifies to [object Object].

Why it fires

In PopChild.mjs:

// Lines 56–61 (framer-motion@12.38.0)
const childRef = children.props?.ref ??
    children?.ref;
const composedRef = useComposedRefs(ref, childRef);

childRef and composedRef are only ever used when pop !== false:

: React.cloneElement(children, { ref: composedRef })

When pop === false (the default for mode="sync" and mode="wait"), the ref is computed and composed but never passed anywhere. However, the unconditional children.props?.ref read still triggers the React 18.3 warning getter for any motion.* element that was created with a ref prop.

Minimal repro

Any AnimatePresence without mode="popLayout" that wraps a motion.div with a ref:

const ref = useRef(null)

<AnimatePresence>
  {isVisible && (
    <motion.div key="content" ref={ref} exit={{ opacity: 0 }}>
      Hello
    </motion.div>
  )}
</AnimatePresence>

Console output in React 18.3:

Warning: [object Object]: `ref` is not a prop.

Stack trace points to PopChildPresenceChildAnimatePresence.

Proposed fix

Only access children.props?.ref when pop !== false, since the ref is only needed in the cloneElement branch:

-const childRef = children.props?.ref ??
-    children?.ref;
+const childRef = pop !== false
+    ? (children.props?.ref ?? children?.ref)
+    : undefined;
 const composedRef = useComposedRefs(ref, childRef);

Environment

  • motion / framer-motion: 12.38.0 (also reproducible on latest 12.40.0main branch still has the unconditional read)
  • React: 18.3.1
  • The warning only fires once globally per app run due to specialPropRefWarningShown = true, but it appears in every test run that renders a modal/sheet/panel backed by AnimatePresence.

Notes

  • The CHANGELOG mentions fixes for popLayout + React 19 ref access (v12.23.16, v12.24.5), but this is a separate issue: it affects non-popLayout mode under React 18.3, not React 19.
  • We are currently working around this with a pnpm patch on our end.

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