首页 > 解决方案 > React:“警告:无法在未安装的组件上执行 React 状态更新” - 尝试更新时通过上下文状态

问题描述

我的应用组件:

class App extends React.Component {
    state = {
        user: {},
        statusMessage: null,
        statusType: null,
        setStatusMessage: () => {}
    }

    renderStatusMessage = () => {
        if (this.state.statusMessage) {
            return <div className={"alert alert-" + this.state.statusType}>{this.state.statusMessage}</div>
        }
        return null;
    }

    render() {
        return (
            <div className="App container">
                <React.StrictMode>
                    <AppContext.Provider value={this.state}>
                        <BrowserRouter>
                            {this.renderStatusMessage()}
                            <IssueBrowser />
                        </BrowserRouter>
                    </AppContext.Provider>
                </React.StrictMode>
            </div>
        );
    }
}

我试图调用的组件this.context.setStatusMessage

export default class UserRegistration extends React.Component {
    state = {
        redirect: false }
    static contextType = AppContext

    registerUser = (event) => {
        event.preventDefault()

        // TODO: check if password matches confirmation, display error message
        // if it doesn't

        let payload = {
            first_name: event.target.first_name.value,
            last_name: event.target.last_name.value,
            email_address: event.target.email_address.value,
            password: event.target.password.value }

        Users.create(payload)
            .then(response => { 
                this.context.setStatusMessage("success", "User successfully registered.")
                this.setState({redirect: true}) })
            .catch(error => { console.log(error) })
    }

    renderRedirect = () => {
        if (this.state.redirect) {
            return <Redirect to="/" />
        }
    }

    render() {
        return (
            <form onSubmit={this.registerUser}>
                {this.renderRedirect()}
                <div className="form-group">
                    <label htmlFor="first_name">First Name:</label>
                    <input name="first_name" type="text" className="form-control" />
                </div>
                <div className="form-group">
                    <label htmlFor="last_name">Last Name:</label>
                    <input name="last_name" type="text" className="form-control" />
                </div>
                <div className="form-group">
                    <label htmlFor="email_address">Email Address:</label>
                    <input name="email_address" type="text" className="form-control" />
                </div>
                <div className="form-group">
                    <label htmlFor="password">Password:</label>
                    <input name="password" type="password" className="form-control" />
                </div>
                <div className="form-group">
                    <label htmlFor="password">Confirm Password:</label>
                    <input name="password" type="password" className="form-control" />
                </div>
                <div className="form-group">
                    <input className="btn btn-primary form-control" type="submit" value="Register" />
                </div>
            </form>
        )
    }
}

警告:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
    in UserRegistration (at IssueBrowser.js:90)
    in Route (at IssueBrowser.js:89)
    in Switch (at IssueBrowser.js:88)
    in MainSwitch (at IssueBrowser.js:117)

我认为它<App />总是挂载的,因为它是根组件。

这是代码<IssueBrowser />

export default class IssueBrowser extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            issues: [],
            redirect: false,
            loading: true,
            noResponse: false
        }
    }

    render() {
        const NewIssueForm = (props) => {
            return (
                <form onSubmit={this.createIssue}>
                    <div className="form-group">
                        <label htmlFor="title">Title</label>
                        <input className="form-control" name="title" type="text" />
                    </div>
                    <div className="form-group">
                        <label htmlFor="body">Body</label>
                        <textarea name="body" className="form-control" rows="10"></textarea>
                    </div>
                    <input type="submit" className="btn btn-primary" />
                </form>
            )
        }

        const IssueDetail = (props) => {
            let {id} = useParams()
            let issue = this.state.issues.find(issue => issue.id === id)

            if (issue) {
                return (
                    <div>
                        <h1 className="card-title">{issue.title}</h1>
                        <p className="card-text">{issue.body}</p>
                        <button className="btn btn-primary" onClick={(e) => this.deleteIssue(issue.id)}>Delete</button>
                    </div>
                )
            } else {
                return null
            }
        }

        const IssueListItem = (props) => {
            return (
                <li className="list-group-item" key={props.issue.id}>
                    <Link to={"/issues/" + props.issue.id}>{props.issue.title}</Link>
                </li>
            )
        }

        const IssueList = (props) => {
                if (this.state.issues && this.state.issues.length > 0) {
                    return (
                        <ul className="list-group list-group-flush">
                            {this.state.issues.map(issue => <IssueListItem key={issue.id} issue={issue} />)}
                        </ul>
                    )
                } else if (!this.state.noResponse) {
                    return (
                        <div>
                            There are no issues. Aren't you lucky?
                        </div>
                    )
                }
                return null;
        }

        const MainSwitch = (props) => {
            if (this.state.loading) {
                return (
                    <div align="center">
                        <LoaderWidget />
                    </div>
                )
            } else {
                return (
                    <Switch>
                        <Route exact path="/users/registration">
                            <UserRegistration />
                        </Route>

                        <Route exact path="/issues/create/">
                            {this.state.redirect ? <Redirect to="/" /> : null}
                            <NewIssueForm />
                        </Route>

                        <Route exact path="/issues/:id">
                            {this.state.redirect ? <Redirect to="/" /> : null}
                            <IssueDetail />
                        </Route>

                        <Route exact path="/">
                            <IssueList />
                        </Route>

                    </Switch>
                )
            }
        }

        return (
            <div className="mt-5">
                <this.Navigation />
                <this.NoResponseFromServer noResponse={this.state.noResponse} />
                <div className="p-3 border border-top-0 rounded-bottom">
                    <MainSwitch />
                </div>
            </div>
        )
    }

    deleteIssue = (issue_id) => {
        this.setState({redirect: true, loading: true})
        Issues.delete(issue_id)
            .then(response => { this.refreshIssues() })
    }

    createIssue = (event) => {
        event.preventDefault()

        let payload = {
            title: event.target.title.value,
            body: event.target.body.value }

        this.setState({redirect: true, loading: true})
        Issues.create(payload)
            .then(response => { console.log(this.state); this.issueCreated() })
            .catch(error => { console.log(error) })
    }

    issueCreated = () => {
        this.refreshIssues();
    }

    setFlag = (flagKey, flagValue) => {
        this.setState({[flagKey]: flagValue})
    }

    refreshIssues = () => {
        Issues.getAll()
            .then(issues => {
                this.setState({issues, loading: false})
            })
            .catch(error => {
                if (!error.response) { this.setFlag('noResponse', true) }
            })
    }

    componentDidMount() {
        this.refreshIssues()
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        // Have to clear redirect flag here. The redirect happens when the HTML
        // is re-rendered, because redirects happen via <Redirect /> tag. but we
        // were clearing the flag before any re-rendering happened, meaning
        // <Redirect /> was never even being rendered in the first place. So
        // after a re-render takes place, check to see if redirect = true, then
        // clear it. Doing it this way gives the <Redirect /> component a chance
        // to be rendered before being cleared.
        if (prevState.redirect) {
            this.setState({redirect: false})
        }
    }


    NoResponseFromServer = (props) => {
        if (this.state.noResponse) {
            return (
                <div className="alert alert-danger">
                    No response received from server. Is it running?
                </div>
            )
        } else {
            return null;
        }
    }

    Navigation = (props) => {
        return (
            <nav className="nav nav-tabs">
                <li className="nav-item"><NavLink to="/" exact={true} className="nav-link" activeClassName="active">All</NavLink></li>
                <li className="nav-item"><NavLink to="/issues/create/" className="nav-link" activeClassName="active">New</NavLink></li>
                <li className="nav-item"><NavLink to="/users/registration/" className="nav-link" activeClassName="active">Register</NavLink></li>
            </nav>
        )
    }
}

function LoaderWidget(props) {
    return (
        <img src={loader_gif} width="50" alt="loading..." />
    )
}

标签: reactjs

解决方案


您在组件中有一个异步调用Users.create(payload),然后设置组件的状态。您只能在组件仍被渲染/安装时设置状态。为此,您需要跟踪组件安装状态。你可以这样做:

componentDidMount() { 
  this._isMounted = true;
}

componentWillUnmount() {
   this._isMounted = false;
}

并且只有 setState if this._isMounted


推荐阅读