Is there any way to fix errors caused by Bootstrap when upgrading to React 18

Viewed 76

I am following a guide to upgrade to React 18. After completing the upgrade I am seeing errors on certain pages in my app.

ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported in React 18.

I am not using the unstable_renderSubtreeIntoContainer() function anywhere in my app, but when I look closer at what is causing these errors it seems to be caused my Bootstrap components.

Is there anyway to update this to remove the errors?

1 Answers

I ran into the same problem with react-bootstrap@v0.33.1 specifically when using OverlayTrigger component after upgrading to React 18. The warning message suggests to migrate to using portals. So I implemented a CustomOverlayTrigger component that leverages portals and referred to React's portal documentation to do so. Note that this solution is for Bootstrap 3 usage of OverlayTrigger (react-bootstrap v0.33.1). It seems later versions of react-bootstrap got rid of using ReactDOM.unstable_renderSubtreeIntoContainer. If you are not in a position to migrate to later versions (like I am), this solution will help for this use case. I have not check thoroughly if other components use the deprecated method, but the approach might be the same.

First of all, I copied the original source of the OverlayTrigger component code located here. You will need to clean up the imports and include into your code the utils function createChainedFunction located here.

I then created a portal wrapper based off React's documentation that looks like this:

import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';

const tooltipRoot = document.getElementById('tooltip-root');

class PortalWrapper extends React.Component {
  constructor(props) {
    super(props);
    this.el = document.createElement('div');
  }

  componentDidMount() {
    tooltipRoot.appendChild(this.el);
  }

  componentWillUnmount() {
    tooltipRoot.removeChild(this.el);
  }

  render() {
    // eslint-disable-next-line react/destructuring-assignment
    return ReactDOM.createPortal(this.props.children, this.el);
  }
}

PortalWrapper.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.node,
  ]).isRequired,
};

export default PortalWrapper;

At the top, you can see the line const tooltipRoot = document.getElementById('tooltip-root');, I simply added in my index.html a div next to the react app's root div that will server as the anchor for my portal.

Then, back in the CustomOverlayTrigger component copied from react-bootstrap, I edited it in the follwing manner:

  1. Remove all references to this._overlay and this._mountNode because the PortalWrapper now manages the mounting/unmounting. So I deleted componentDidMount(), componentDidUpdate(), componentWillUnmount() and renderOverlay()

  2. I modified makeOverlay so that its result is wrapped by PortalWrapper so it became the following:

makeOverlay(overlay, props) {
    return (
      <PortalWrapper>
        <Overlay
          {...props}
          show={this.state.show}
          onHide={this.handleHide}
          target={this}
        >
          {overlay}
        </Overlay>
      </PortalWrapper>
    );
  }
  1. Finally, I changed the render method's return statement to become:
return (<>
      {cloneElement(child, triggerProps)}
      {this.makeOverlay(overlay, props)}
    </>);

After this, I simply had to replace all my invocations to OverlayTrigger with CustomOverlayTrigger and I had the same result without the warning message.

Related