当打开对话框时,ReactJS:“无法在已卸载的组件上执行React状态更新”

时间:2019-05-24 07:57:10

标签: reactjs opendialog

从另一个类组件打开对话框时出现错误:“无法在已卸载的组件上执行React状态更新。这是空操作,但是它表明您的应用程序中存在内存泄漏。要修复,请取消componentWillUnmount方法中的所有订阅和异步任务”

index.js

import ...

class AdMenu extends Component {
    componentWillMount = () => {
        this.onSearch();
    };

    onOpenInsert = () => {
        showDetailDialog();
    };

    onSearch = () => {
        fetch(_url, ...)
            .then(response => {
                if (response.ok) {
                    return response.json();
                } else {
                    throw response;
                }
            })
            .then(responseJson => {
                this.setState({...});
            })
            .catch(response => {...});
    };

    render() {
        return (
            <div>
                <DetailDialog />
                <Button color="primary" onClick={this.onOpenInsert}>Add New</Button>
                <BootstrapTable .... />
            </div>
        );
    }
}

export default withTranslation()(AdMenu);

DetailDialog.js


export var showDetailDialog = function () {
    this.setState({open: true});
    console.log('Mounted: ' + this.mounted);
};

class DetailDialog extends React.Component {
    mounted = false;
    controller = new AbortController();
    constructor(props) {
        super(props);
        this.state = {open: false};
        showDetailDialog = showDetailDialog.bind(this);
    }
    componentDidMount() {
        console.log('componentDidMount');
        this.mounted = true;
    }

    componentWillUnmount(){
        console.log('componentWillUnmount');
        this.mounted = false;
    }
    onClose = () => {
        this.setState({open: false});
    };

    render() {
        return (
            <Modal isOpen={this.state.open} toggle={this.onClose} className={"modal-primary"} >
                <ModalHeader toggle={this.onClose}>Detail</ModalHeader>
                <ModalBody>
                    ...
                </ModalBody>
            </Modal>
        );
    }
}

export default withTranslation()(DetailDialog);

我有一个DetailDialog导出的类组件和函数showDetailDialog。它导入到index.js页面。

当我第一次打开页面并单击“打开对话框”时,工作正常。但是,当我在菜单中通过路由器切换到另一页面,然后在第二次再次打开页面时,控制台日志中出现了错误。

我尝试使用this.mount var检查已卸载的组件,但是我不知道如何在第二次和第二次卸载组件时设置状态以打开详细信息对话框。

我尝试使用controller = new AbortController();和componentWillUnmount()中的controller.abort(),但不起作用。

对此问题有什么解决办法?

谢谢!

图片:https://prnt.sc/nsp251

error image in console log

来自CodeSandbox的来源:https://codesandbox.io/s/coreuicoreuifreereactadmintemplate-5unwj

逐步测试:

  • 点击广告菜单(第一个)

  • 点击广告组

  • 点击广告菜单(第二个)

  • 在“广告”菜单中单击“打开对话框”

  • View Console日志浏览器

文件:src / views / category

节点v11.12.0

Npm 6.7.0

窗口10

2 个答案:

答案 0 :(得分:0)

将您在componentWillMount中编写的逻辑移动到componentDidMount中的AdMenu

答案 1 :(得分:0)

您的问题是使用外部函数showDetailDialog来访问DetailDialog组件的状态。 AdMenu组件中使用的功能与绑定到其构造函数中的DetailDialog组件的功能是不相同的。

一种解决方案是使用Refs并在组件本身上公开open函数。

class DetailDialog extends Component {
    open = () => this.setState({ open: true });
}

/* ... */

class AdMenu extends Component {
    constructor(props) {
        super(props);
        this.detailDialog = React.createRef();
        this.onOpenInsert = this.onOpenInsert.bind(this);
    }

    onOpenInsert() {
        this.detailDialog.current.open();
    }

    render() {
        return (
            <DetailDialog ref={this.detailDialog} />
            { ... }
        );
    }
}

但是React documentation for Refs不推荐这种方法。

  

裁判有一些很好的用例:

     
      
  • 管理焦点,文本选择或媒体播放。
  •   
  • 触发命令性动画。
  •   
  • 与第三方DOM库集成。
  •   
     

避免将refs用于可以声明式完成的任何事情。

     

例如,不要在Dialog组件上公开open()close()方法,而要向其传递isOpen属性。

根据文档的建议,您可以在detailOpen组件上声明一个AdMenu状态,并将其作为DetailDialog属性传递给open组件。

class AdMenu extends Component {
    constructor(props) {
        super(props);
        this.state = {
            detailOpen: false
        }
    }

    onOpenInsert() {
        this.setState({ detailOpen: true });
    }

    onDialogClose() {
        this.setState({ detailOpen: false });
    }

    /* ... */

    render() {
        return (
            <DetailDialog open={this.state.detailOpen} onClose={this.onDialogClose} />
            { ... }
        );
    }
}

/* ... */

class DetailDialog extends Component {
    /* ... */

    render() {
        return (
            <Modal isOpen={this.props.open} toggle={this.props.onClose}>
                <ModalHeader toggle={this.props.onClose}>Detail</ModalHeader>
                <ModalBody>
                    ...
                </ModalBody>
            </Modal>
        );
    }
}

您选择哪种方法取决于您。