Skip to content

fix(ui-motion): only pass elementRef to children that accept it - #2667

Open
drakeaharper wants to merge 1 commit into
masterfrom
fix/ui-motion-element-ref-leak
Open

fix(ui-motion): only pass elementRef to children that accept it#2667
drakeaharper wants to merge 1 commit into
masterfrom
fix/ui-motion-element-ref-leak

Conversation

@drakeaharper

Copy link
Copy Markdown
Collaborator

Summary

BaseTransition.renderChildren() treats typeof child.type === 'object' as "this is a withStyle-decorated InstUI component":

// `typeof type === 'object'` => forwardRef wrapper (withStyle-decorated InstUI components)
const refProps =
  typeof child.type === 'object'
    ? { elementRef: createChainedFunction(...), ref: elementOnlyRef }
    : { ref: (el) => this.handleRef(...) }

But that's true of every forwardRef wrapper. @emotion/react's wrapper matches it and forwards unknown props straight to the DOM node, so any emotion element wrapped in a Transition warns:

Warning: React does not recognize the `elementRef` prop on a DOM element.
    at span
    at @emotion/react/dist/emotion-element…
    at BaseTransition (@instructure/ui-motion/es/Transition/BaseTransition/index.js:52)
    at Transition → DrawerTray → DrawerLayout

DrawerLayout.Tray renders an emotion element inside a Transition, so every tray and dialog built on it logs this. 11.7.4-SECURITY.3 used ref + findDOMNode and never passed elementRef, so this appeared in 11.7.4. Still present on 11.7.5-snapshot-14.

Changes

  • Gate the elementRef branch on the child actually declaring elementRef in allowedProps. InstUI components decorated with withStyle expose it; emotion's wrapper doesn't, so it falls back to the plain ref path — the behaviour that shipped before this check existed.
  • Keep the chaining for children that do accept it, so a child's own elementRef still fires rather than being overwritten.
  • Add three tests under <Transition /> covering ref forwarding.

Verification

  • The first new test fails on unpatched source and passes with the fix. The other two are guards: one proves the child node is still found via the fallback ref, and one proves an allowedProps-declaring child still receives a chained elementRef — that pair would catch over-correcting by dropping the elementRef branch entirely.
  • pnpm run test:vitest ui-motion — 21 passed.
  • pnpm run bootstrap clean, including build:types.

Context

Found upgrading canvas-lms off the Artifactory-only 11.7.4-SECURITY.3 onto public 11.7.4. canvas-rce's vitest setup escalates console.error to a failure, so this alone failed 380 tests across 23 files, all Tray/Dialog specs. Reported in #instui. Canvas is carrying a patch-package patch for this that we'll drop once a fixed release ships.

Alternative worth considering

Gating on allowedProps is the narrowest fix that keeps your new chaining behaviour. If you'd rather not rely on that static, the other options are an explicit opt-in prop on the child, or reverting to the pre-11.7.4 ref + findDOMNode approach and solving the overwrite problem differently. Happy to reshape this if you prefer one of those.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

`BaseTransition` treats `typeof child.type === 'object'` as "this is a
withStyle-decorated InstUI component", but that is true of every
forwardRef wrapper. `@emotion/react`'s wrapper matches it and forwards
unknown props straight to the DOM node, so any emotion element wrapped in
a Transition produces:

    Warning: React does not recognize the `elementRef` prop on a DOM
    element.

DrawerLayout.Tray hits this, so every tray and dialog built on it logs
the warning. Gate the `elementRef` branch on the child declaring
`elementRef` in `allowedProps`; everything else keeps the plain `ref`
path, which is what shipped before this check existed.

Found while upgrading canvas-lms from 11.7.4-SECURITY.3 to 11.7.4, where
it failed 380 tests in canvas-rce, whose test setup treats console.error
as fatal.
@drakeaharper
drakeaharper force-pushed the fix/ui-motion-element-ref-leak branch from d15112c to c32c35f Compare July 30, 2026 05:01
@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://instructure.design/pr-preview/pr-2667/

Built to branch gh-pages at 2026-07-30 05:05 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

github-actions Bot pushed a commit that referenced this pull request Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Visual regression report

⚠️ Changes detected.

Status Count
Unchanged 0
Changed 1
New 96
Removed 32

📊 View full report

Diff images (33)

alert.png — baseline no longer produced

avatar.png — baseline no longer produced

badge.png — baseline no longer produced

billboard.png — baseline no longer produced

breadcrumb.png — baseline no longer produced

button-and-derivatives.png — baseline no longer produced

byline.png — baseline no longer produced

calendar.png — baseline no longer produced

checkbox.png — baseline no longer produced

checkboxgroup.png — baseline no longer produced

colorpicker.png — baseline no longer produced

contextview.png — baseline no longer produced

custom-and-lucide-icons.png — baseline no longer produced

dateinput-dateinput2.png — baseline no longer produced

datetimeinput.png — baseline no longer produced

diff-demo.png — 6324 pixels differ

drilldown.png — baseline no longer produced

filedrop.png — baseline no longer produced

form-errors.png — baseline no longer produced

heading.png — baseline no longer produced

img.png — baseline no longer produced

link.png — baseline no longer produced

menu.png — baseline no longer produced

metric-pill-tag-timeselect-text.png — baseline no longer produced

options.png — baseline no longer produced

pagination.png — baseline no longer produced

progressbar.png — baseline no longer produced

select-simpleselect.png — baseline no longer produced

table.png — baseline no longer produced

tabs.png — baseline no longer produced

tooltip.png — baseline no longer produced

treebrowser.png — baseline no longer produced

view.png — baseline no longer produced

Baselines come from the visual-baselines branch. They refresh on every merge to master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants