Skip to content

5.16.9 breaks mouse events for nested React components in MDX (regression from #15150) #15251

@cbliu1127

Description

@cbliu1127

Describe the Bug

In Astro 5.16.9, React components with mouse event handlers (e.g., onMouseDown, onMouseMove, onMouseUp) stop working when nested inside HTML elements in MDX files. This is a regression introduced by PR #15150.

Steps to Reproduce

In an MDX file:

<div className="wrapper">
  <InteractiveComponent 
    onMouseDown={(e) => console.log('mousedown')}
    onMouseMove={(e) => console.log('mousemove')}
    client:load
  />
</div>

InteractiveComponent.tsx:

export const InteractiveComponent = ({ onMouseDown, onMouseMove }) => {
  return (
    <div 
      onMouseDown={onMouseDown}
      onMouseMove={onMouseMove}
    >
      Drag me
    </div>
  );
};

Expected Behavior

  • 5.16.8 and earlier: Mouse events work correctly
  • 5.16.6: Mouse events work correctly

Actual Behavior

  • 5.16.9: Mouse events do not fire, component renders static HTML without hydration

Root Cause Analysis

PR #15150 modified the array rendering logic in packages/astro/src/runtime/server/jsx.ts:

Before (5.16.8):

case Array.isArray(vnode):
  return markHTMLString(
    (await Promise.all(vnode.map((v: any) => renderJSX(result, v)))).join(''),
  );

After (5.16.9):

case Array.isArray(vnode): {
  const renderedItems = await Promise.all(vnode.map((v: any) => renderJSX(result, v)));
  let instructions: RenderInstruction[] | null = null;
  let content = '';
  for (const item of renderedItems) {
    if (item instanceof SlotString) {
      content += item;
      instructions = mergeSlotInstructions(instructions, item);
    } else {
      content += item;  // ← Components treated as plain strings
    }
  }
  // ...
}

The problem: When React components are nested in HTML elements within MDX, the children array processing now treats non-SlotString items as plain HTML strings, stripping the hydration mechanism (no <astro-island> tags are output).

Workaround

Move the className to the component props instead of wrapping in a div:

<InteractiveComponent 
  className="wrapper"
  onMouseDown={(e) => console.log('mousedown')}
  client:load
/>

This avoids the array processing logic that causes the issue.

Impact

  • Breaks all React components with event handlers when nested in MDX
  • Affects third-party components that cannot be easily modified
  • Workaround is not always feasible (semantic HTML structure, layout requirements)

Environment

Suggested Fix

The array rendering logic should preserve hydration for all rendered components, not just SlotString instances. The fix for MDX slots inadvertently broke non-slot scenarios.

Metadata

Metadata

Assignees

Labels

- P4: importantViolate documented behavior or significantly impacts performance (priority)pkg: reactRelated to React (scope)regression

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions