Skip to content

MM-69835 Remove all usage of findDOMNode - #11

Merged
hmhealey merged 9 commits into
MM-69835-2from
MM-69835-3
Aug 11, 2026
Merged

MM-69835 Remove all usage of findDOMNode#11
hmhealey merged 9 commits into
MM-69835-2from
MM-69835-3

Conversation

@hmhealey

@hmhealey hmhealey commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

The last big change to get this working with React 19 is to remove usage of findDOMNode. This is also the one that's the most likely to be a breaking change because it makes it so that we need to pass HTML elements around or use querySelector to get them which I did in a few places.

I'm mostly relying on the tests in mattermost/mattermost#37758 to ensure that I haven't broken anything here, so fingers crossed we don't run into any issues with how plugins or anything else use these components.

Ticket Link

https://mattermost.atlassian.net/browse/MM-69835

@hmhealey hmhealey changed the title Mm 69835 3 Remove all usage of findDOMNode Aug 4, 2026
@hmhealey hmhealey changed the title Remove all usage of findDOMNode MM-69835 Remove all usage of findDOMNode Aug 4, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current versions of react-overlays and react-transition-group both use findDOMNode, so I've had to update them. In some cases, they still might try to call it if we pass a React element as a prop instead of an HTMLElement, but my hope is that we only ever use them through react-bootstrap so that won't happen.

The changes here are just porting the previous patch over to the new version of react-overlays. Note that this patch doesn't actually affect the code used at runtime, and the web app is going to actually have its own patch for this library

Comment thread src/CarouselItem.js
return <div {...props} className={classNames(className, classes)} />;
return (
<div
ref={this.containerRef}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these are straightforward because there's already a DOM node that we can attach a ref to instead of using findDOMNode

Comment thread src/Collapse.js
const handleExit = createChainedFunction(this.handleExit, onExit);
const handleExiting = createChainedFunction(this.handleExiting, onExiting);

const ref = makeMergedRef([this.childRef, getElementRef(children)]);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a case where we need to inject a ref and hope that it returns a DOM node. I think this may cause an error if Transition doesn't forward its ref to an HTML element, but I don't think we pas anything as Transition other than Fade and Collapse, both of which now forward refs

Comment thread src/Dropdown.js

focus() {
const toggle = ReactDOM.findDOMNode(this.toggle);
const toggle = this.containerRef.current.querySelector(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, I didn't think we can pass a ref around for this, so I went with searching the DOM and hoping that the a11y for the toggle button is set up correctly. We use this component in 1 place in the web app (which has tests thankfully) and one or two plugins, so I'm hoping this doesn't break

Comment thread src/DropdownMenu.js
}
}

function RootCloseWrapper({

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new version of react-overlays doesn't have its own RootCloseWrapper, and I think it's too much work to migrate this to a functional component

Comment thread src/Modal.js
function DialogTransition(props) {
return <Fade {...props} timeout={Modal.TRANSITION_DURATION} />;
}
const DialogTransition = React.forwardRef((props, ref) => (

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These forwardRefs aren't actually needed once we're using React 19, but I wanted to make sure this all works with React 18 to ease in the upgrade

Comment thread src/Modal.js
prefix(props, 'backdrop'),
backdropClassName,
inClassName
renderBackdrop={backdropProps => (

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a change in the API for the react-overlays Modal because:

  1. The backdropClassName prop was replaced by renderBackdrop
  2. Instead of passing a child that gets cloned to inject props, there's now a renderDialog prop.

I think I've got them both working the same now since I didn't want to change the API of our React Bootstrap components at all

Comment thread src/ModalDialog.js
Comment on lines +21 to +22
bsClass: PropTypes.string,
bsSize: PropTypes.oneOf(SIZES)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should've been in #12, so just ignore them for now

Comment thread src/Overlay.js Outdated
{child}
<BaseOverlay {...props} target={null} transition={transition}>
{({ props: overlayProps }) => (
<div ref={overlayProps.ref} style={{ display: 'content' }}>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, I didn't have an option for attaching a ref to the arbitrary child or using a11y attributes, so I instead opted to add an extra div and just hope that it can properly match the size of its child by using display: content

@hmhealey

hmhealey commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai Review this

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

@hmhealey I will review pull request #11. I will focus on the findDOMNode replacements and compatibility risks.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change updates overlay dependencies and Escape handling. It replaces findDOMNode usage with explicit refs, adopts newer overlay render APIs, converts transition components to forwarded refs, and updates related tests.

Changes

React ref and overlay modernization

Layer / File(s) Summary
Shared overlay contracts
package.json, patches/react-overlays+5.2.1.patch, src/utils/mergeRefs.js
Dependencies and patched overlay builds support updated transition APIs, modern Escape events, and merged React refs.
Component DOM refs
src/CarouselItem.js, src/Collapse.js, src/Nav.js
Components use explicit refs for transition cleanup, collapse handlers, navigation lookup, and child ref forwarding.
Dropdown and overlay flow
src/Dropdown.js, src/DropdownMenu.js, src/Overlay.js, src/OverlayTrigger.js, test/OverlayTriggerSpec.js
Dropdown and overlay components use container refs, merged refs, render props, useRootClose, and modern context tests.
Modal and transition components
src/Fade.js, src/Modal.js, src/ModalDialog.js, test/FadeSpec.js, test/ModalSpec.js, test/index.js
Fade and modal components use forwarded refs and updated render contracts. Tests inspect rendered elements and no longer suppress findDOMNode warnings.

Estimated code review effort: 4 (Complex) | ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: removing all usage of findDOMNode.
Description check ✅ Passed The description explains the findDOMNode removal, React 19 compatibility goal, potential breaking changes, testing, and ticket reference.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch MM-69835-3

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

package.json

Parsing error: Unexpected token, expected ";"

1 | {

2 | "name": "react-bootstrap",
| ^
3 | "version": "0.32.4",
4 | "description": "Bootstrap 3 components built with React",
5 | "repository": {


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Collapse.js (1)

201-216: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore the DOM node in Collapse transition callbacks.

With react-transition-group@4.4.5, nodeRef omits the DOM node. Enter callbacks receive isAppearing, exit callbacks receive undefined, and addEndListener receives only done. Wrap all exposed callbacks to pass this.childRef.current, and add regression tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Collapse.js` around lines 201 - 216, Update the callback wrappers in
Collapse’s transition setup to prepend this.childRef.current to onEnter,
onEntering, onEntered, onExit, onExiting, and addEndListener arguments,
preserving isAppearing for enter callbacks and undefined for exit callbacks.
Ensure consumers receive the DOM node despite nodeRef, and add regression
coverage for these callback signatures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@package.json`:
- Line 129: Update TabPane’s caller-supplied transition rendering to create and
pass a DOM nodeRef to the transition, and attach that ref to the pane element so
custom transitions avoid findDOMNode. Preserve the existing Fade, Collapse, and
modal transition behavior, while requiring custom transition components to
consume the provided nodeRef.

In `@src/Fade.js`:
- Around line 72-101: Update the Fade transition callbacks around the Transition
usage to restore the DOM-node first argument omitted when nodeRef is provided.
Wrap onEnter, onEntering, onEntered, onExit, onExiting, onExited, and
addEndListener so each invokes the supplied callback with childRef.current first
while preserving remaining arguments and existing behavior, including
Modal/DialogTransition forwarding. Add regression coverage for enter, exit, and
addEndListener callbacks.

In `@src/Modal.js`:
- Around line 279-294: Restore compatibility for dialogComponentClass values
that do not forward refs by preserving the previous wrapper behavior around the
Dialog render path. Ensure this._modal.dialog consistently references the
expected DOM dialog element so updateStyle() works when the modal enters, while
retaining support for ref-forwarding components and existing styling props.

In `@src/Overlay.js`:
- Around line 91-96: Update the BaseOverlay invocation and rendered overlay
element in Overlay: remove target={null} so triggerRef is passed through, and
spread the generated overlayProps onto the div alongside its ref and existing
style. Preserve the child rendering and current display style.

In `@src/OverlayTrigger.js`:
- Around line 297-299: Update the trigger rendering in OverlayTrigger so
triggerRef attaches to the actual geometry-bearing trigger DOM node via ref
forwarding or the established render-prop contract, rather than relying on
invalid display:'content'. If arbitrary children require a wrapper, give it an
explicit measurable inline layout contract and preserve trigger positioning; add
a regression test covering inline-trigger geometry.

In `@src/utils/mergeRefs.js`:
- Around line 10-20: Update makeMergedRef to retain cleanup functions returned
by callback refs, and return a composed cleanup that clears object refs, invokes
each returned cleanup, and calls ref(null) only for callbacks without cleanup.
Ensure useMergedRef uses this updated behavior without discarding the returned
cleanup.

---

Outside diff comments:
In `@src/Collapse.js`:
- Around line 201-216: Update the callback wrappers in Collapse’s transition
setup to prepend this.childRef.current to onEnter, onEntering, onEntered,
onExit, onExiting, and addEndListener arguments, preserving isAppearing for
enter callbacks and undefined for exit callbacks. Ensure consumers receive the
DOM node despite nodeRef, and add regression coverage for these callback
signatures.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8bad325a-fb9c-481e-a107-91abbddd8205

📥 Commits

Reviewing files that changed from the base of the PR and between b5d10b9 and 2dd3aae.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (19)
  • package.json
  • patches/react-overlays+0.9.3.patch
  • patches/react-overlays+5.2.1.patch
  • src/CarouselItem.js
  • src/Collapse.js
  • src/Dropdown.js
  • src/DropdownMenu.js
  • src/Fade.js
  • src/Modal.js
  • src/ModalDialog.js
  • src/Nav.js
  • src/Overlay.js
  • src/OverlayTrigger.js
  • src/utils/mergeRefs.js
  • test/DropdownSpec.js
  • test/FadeSpec.js
  • test/ModalSpec.js
  • test/OverlayTriggerSpec.js
  • test/index.js
💤 Files with no reviewable changes (3)
  • test/DropdownSpec.js
  • test/index.js
  • patches/react-overlays+0.9.3.patch

Comment thread package.json
Comment thread src/Fade.js
Comment thread src/Modal.js
Comment thread src/Overlay.js Outdated
Comment thread src/OverlayTrigger.js Outdated
Comment thread src/utils/mergeRefs.js
I removed the container prop from Modal, but we don't seem to use that ourselves
so it's probably fine to do. It was marked as private anyway.
This used to come from react-overlays, but it's not part of the newer version.
- Fix TabPane transition
- Fix callbacks to transitions to pass element as first parameter
- Remove dialogComponentClass prop from Modal
…reaking change)

Breaking change: overlay prop of OverlayTrigger and child of Overlay must forward ref
to a DOM node. It will cause tooltips to either not appear or not be correctly
positioned in affected plugins.

The only one of our plugins that should be affected is mattermost-plugin-legal-hold. To fix
it, its Tooltip must be updated to forward a ref to the inner RBTooltip, and OverlayTrigger
must similarly pass a ref to the child of OverlayWrapper.
This fixes my previous attempt because the div didn't have a box to get the dimensions for by giving
it dimensions. I'm not sure if this will work 100% because the child element can be either inline or
block, but this seems to work based on some brief testing. To fully match the old behaviour, we'd
attach the ref to the cloned child, but that would be a breaking change to a few plugins.
@hmhealey
hmhealey requested a review from calebroseland August 6, 2026 17:26
@hmhealey
hmhealey merged commit ed31a21 into 0.34 Aug 11, 2026
1 check passed
@hmhealey
hmhealey deleted the MM-69835-3 branch August 11, 2026 15:37
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