React 16: Error: Unable to find node on an unmounted component
Asked Answered
B

2

13

I recently upgraded my project from React v15.2.1 to 16.4.1 and my Sidebar component is throwing the following error:

Error: Unable to find node on an unmounted component. bundle.js line 1326 > eval:42:15
invariant
webpack:///./node_modules/fbjs/lib/invariant.js?:42:15
findCurrentFiberUsingSlowPath
webpack:///./node_modules/react-dom/cjs/react-dom.development.js?:3817:7
findCurrentHostFiber
webpack:///./node_modules/react-dom/cjs/react-dom.development.js?:3886:23
findHostInstance
webpack:///./node_modules/react-dom/cjs/react-dom.development.js?:16824:19
findDOMNode
webpack:///./node_modules/react-dom/cjs/react-dom.development.js?:17310:12
handleClickOutside
webpack:///./src/components/simulator/sidebar/Sidebar.js?:99:31
handleClickOutside self-hosted:984:17

Based on the error message, I believe the error is happening when calling ReactDOM.findDOMNode(this) in the handleClickOutside(event) method.

The component that I am using can be found here: https://ashiknesin.com/blog/build-custom-sidebar-component-react/, I have changed it a little bit to this:

import React from 'react'
import ReactDOM from 'react-dom'
import classNames from 'classnames'
import SimulatorForm from './SimulatorForm'
import './Sidebar.scss'

const  openForm = require('../../../public/icons/si-glyph-arrow-left.svg');
const closeForm = require('../../../public/icons/si-glyph-arrow-right.svg');
const   pinForm = require('../../../public/icons/si-glyph-pin-location-love.svg');
const unpinForm = require('../../../public/icons/si-glyph-pin-location-delete.svg');

class Sidebar extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            showMenu: false,
            isMenuPinned: false,
            formIcon: openForm,
            pinIcon: pinForm,
            modelsDescription: props.modelsDescription
        }
        // Methods to pin/unpin the Form
        this.toggleMenu = this.toggleMenu.bind(this)
        this.pinMenu = this.pinMenu.bind(this)
        // Handlers
        this.handleSelectedModelChange = this.props.handleSelectedModelChange
        this.handleNewMasterGraphsData = this.props.handleNewMasterGraphsData
        this.handleNewResults = this.props.handleNewResults
    }
    componentWillReceiveProps(nextProps) {
        this.setState({ modelsDescription: nextProps.modelsDescription});
    }
    componentDidMount() {
        document.addEventListener('click', this.handleClickOutside.bind(this), true);
    }

    componentWillUnmount() {
        document.removeEventListener('click', this.handleClickOutside.bind(this), true);
    }
    pinMenu() {
        this.setState({
            isMenuPinned: !this.state.isMenuPinned,
            pinIcon: this.state.isMenuPinned ? pinForm : unpinForm
        });
    }
    toggleMenu() {
        this.setState({
            showMenu: !this.state.showMenu,
            formIcon: this.state.showMenu ? openForm : closeForm
        });
    }
    handleClickOutside(event) {
        if (!this.state.isMenuPinned) {
            const domNode = ReactDOM.findDOMNode(this);

            if ((!domNode || !domNode.contains(event.target))) {
                this.setState({
                    showMenu: false,
                    formIcon: openForm
                });
            }
        }
    }

    render() {
        const showMenu = this.state.showMenu;
        const sidebarClass = classNames({
            'sidebar': true,
            'sidebar-menu-expanded': showMenu,
            'sidebar-menu-collapsed': !showMenu
        });

        const elementsClass = classNames({
            'expanded-element': true,
            'is-hidden': !showMenu,
        });

        return (
            <nav className={sidebarClass}>
                <img className="menuIcon" src={this.state.formIcon} height="42" width="42" onClick={this.toggleMenu} />
                <ul>
                    <li>
                        {
                            this.state.showMenu ? <img className="pinIcon" src={this.state.pinIcon}  height="42" width="42" onClick={this.pinMenu} /> : null
                        }
                    </li>
                    <li>
                        {
                            this.state.showMenu ?  <SimulatorForm modelsDescription={this.state.modelsDescription} handleSelectedModelChange={this.handleSelectedModelChange}
                                                    handleNewMasterGraphsData={this.handleNewMasterGraphsData} handleNewResults={this.handleNewResults} /> : null
                        }        
                    </li>
                </ul>
            </nav>
        )
    }
}


export default Sidebar

Lastly, the error is only thrown when I reload the page. If I don't, it seems to be working perfectly fine. Do you have any suggestions or recommendations to make sure the error is not thrown again?

I have been reading about this on-line and I couldn't find a fix for it. Also, I dont't think this is listed as a breaking change but I could be very wrong.

Bertie answered 16/7, 2018 at 15:31 Comment(2)
Binding a function creates a new function object, and even though the one you pass when you add a handler and the one you pass when you want to remove that handler seem to be the same, they're not considered equal, so you end up not removing that handler. Storing a bound function in a property by setting it in the constructor is one of the ways to resolve your issue.Serum
Hello, I have the same problem as you and I am wondering why this problem is incidental? As in your example, this problem is only triggered when you reload the page. Why? Looking forward to hearing from you soon. Thanks.Lastditch
B
8

I ended up finding the solution thanks to the help of the original writer of the code. Link can be found here. It was a problem with my bindings, more details on the link.

I have changed:

componentDidMount() {
   document.addEventListener('click', this.handleClickOutside.bind(this), true);
}

componentWillUnmount() {
   document.removeEventListener('click', this.handleClickOutside.bind(this), true);
}

to

componentDidMount = () => {
   document.addEventListener("click", this.handleClickOutside, true);
};

componentWillUnmount = () => {
   document.removeEventListener("click", this.handleClickOutside, true);
};
Bertie answered 18/7, 2018 at 11:35 Comment(1)
Normally the callbacks would be using arrow fns handleClickOutside = () => { /*...*/ } then it should be ok too I would thinkBushman
O
0

I just use material ui Menu from another package and got same issue , it strange but installing latest (similar to the spa version) react-dom solve the issue.

Orthotropous answered 11/2, 2022 at 16:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.