fix(useForwardExpose): sync currentElement on DOM update#2475
fix(useForwardExpose): sync currentElement on DOM update#2475
currentElement on DOM update#2475Conversation
📝 WalkthroughWalkthroughModifies Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/core/src/shared/useForwardExpose.test.ts (1)
299-324: Add one update-cycle test for direct DOM refs as well.This case validates the
asChildcomponent-instance path well. Please add a companion case whereforwardRefreceives a plainElementand the component re-renders, so the newonUpdatedsync path is protected end-to-end too.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/src/shared/useForwardExpose.test.ts` around lines 299 - 324, Add a companion test to the existing "should update currentElement when as-child conditional child swaps" case that exercises the direct DOM ref path: mount a component that uses useForwardExpose() and assigns forwardRef to a plain Element (not a component instance) via asChild or direct ref, track currentElement.value.nodeName in a post-effect (like renderNode), toggle between two different DOM tags (e.g., 'button' and 'span'), flush promises, and assert the nodeName updates accordingly; reference useForwardExpose, forwardRef, currentElement, Primitive and the existing test pattern to mirror the setup and toggling logic so the onUpdated sync path for direct Element refs is validated end-to-end.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/core/src/shared/useForwardExpose.ts`:
- Around line 21-24: In resolveCurrentElement, guard the $el property before
reading nodeName to avoid crashes when forwardRef receives a plain DOM element:
check for currentRef.value?.$el (or use optional chaining
currentRef.value?.$el?.nodeName) before comparing against ['#text', '#comment'],
and fall back to unrefElement(currentRef) when $el is missing; update the
conditional in resolveCurrentElement (referencing currentRef, $el, nodeName,
nextElementSibling, and unrefElement) so it only accesses nodeName if $el exists
and returns the appropriate element otherwise.
---
Nitpick comments:
In `@packages/core/src/shared/useForwardExpose.test.ts`:
- Around line 299-324: Add a companion test to the existing "should update
currentElement when as-child conditional child swaps" case that exercises the
direct DOM ref path: mount a component that uses useForwardExpose() and assigns
forwardRef to a plain Element (not a component instance) via asChild or direct
ref, track currentElement.value.nodeName in a post-effect (like renderNode),
toggle between two different DOM tags (e.g., 'button' and 'span'), flush
promises, and assert the nodeName updates accordingly; reference
useForwardExpose, forwardRef, currentElement, Primitive and the existing test
pattern to mirror the setup and toggling logic so the onUpdated sync path for
direct Element refs is validated end-to-end.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/core/src/shared/useForwardExpose.test.tspackages/core/src/shared/useForwardExpose.ts
| function resolveCurrentElement(): HTMLElement { | ||
| // $el could be text/comment for non-single root normal or text root, thus we retrieve the nextElementSibling | ||
| // @ts-expect-error ignore ts error | ||
| return ['#text', '#comment'].includes(currentRef.value?.$el.nodeName) ? currentRef.value?.$el.nextElementSibling : unrefElement(currentRef) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n packages/core/src/shared/useForwardExpose.tsRepository: unovue/reka-ui
Length of output: 3820
Guard $el before reading nodeName to avoid update-time crashes.
If forwardRef receives a plain DOM element, it lacks a $el property. The expression currentRef.value?.$el.nodeName only guards against null/undefined on currentRef.value itself, not on the undefined $el property that follows. This throws when accessed during the onUpdated hook triggered by component updates.
🐛 Proposed fix
function resolveCurrentElement(): HTMLElement {
// $el could be text/comment for non-single root normal or text root, thus we retrieve the nextElementSibling
// `@ts-expect-error` ignore ts error
- return ['#text', '#comment'].includes(currentRef.value?.$el.nodeName) ? currentRef.value?.$el.nextElementSibling : unrefElement(currentRef)
+ const target = currentRef.value instanceof Element
+ ? currentRef.value
+ : currentRef.value?.$el
+
+ if (!target)
+ return undefined
+
+ return ['#text', '#comment'].includes(target.nodeName)
+ ? target.nextElementSibling
+ : target
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/shared/useForwardExpose.ts` around lines 21 - 24, In
resolveCurrentElement, guard the $el property before reading nodeName to avoid
crashes when forwardRef receives a plain DOM element: check for
currentRef.value?.$el (or use optional chaining currentRef.value?.$el?.nodeName)
before comparing against ['#text', '#comment'], and fall back to
unrefElement(currentRef) when $el is missing; update the conditional in
resolveCurrentElement (referencing currentRef, $el, nodeName,
nextElementSibling, and unrefElement) so it only accesses nodeName if $el exists
and returns the appropriate element otherwise.
🔗 Linked issue
resolves #2474
❓ Type of change
📚 Description
📸 Screenshots (if appropriate)
📝 Checklist
Summary by CodeRabbit
Bug Fixes
Tests