Can't call setState (or forceUpdate) on an unmounted component
Asked Answered
B

2

22

I'm trying to fetch the data from the server after component has been updated but I couldn't manage to do that. As far as I understand componentWillUnmount is called when component is about to be destroyed, but I never need to destroy it so it's useless to me. What would be solution for this? When I should set the state?

async componentDidUpdate(prevProps, prevState) {
  if (this.props.subject.length && prevProps.subject !== this.props.subject) {
    let result = await this.getGrades({
      student: this.props.id,
      subject: this.props.subject
    });
    this.setState({
      subject: this.props.subject,
      grades: result
    });
  }
}

async getGrades(params) {
  let response, body;

  if (params['subject'].length) {
    response = await fetch(apiRequestString.gradesBySubject(params));
    body = await response.json();
  } else {
    response = await fetch(apiRequestString.grades(params));
    body = await response.json();
  }

  if (response.status !== 200) throw Error(body.message);

  return body;
}

Full error:

Warning: Can't call setState (or forceUpdate) 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.
Bastien answered 19/5, 2018 at 19:39 Comment(4)
I would place a breakpoint (if using Chrome dev tools and sourcemaps) and see when does setState get called and whether the component is unmounted before getGrades resolves into result. For debugging this might help when exploring the component in question.Cellulose
Clearly, React is destroying your component, so »my component never gets destroyed« doesn't appear to be a valid assumption.Salmon
I wasn't aware of that. When it happens?Kirsch
This is most likely a false warning - which is why the React team will remove the warning in the next release. See PRStratify
D
46

A common pattern I use in this instance is something along the lines of

componentWillUnmount() {
    this.isCancelled = true;
}

And then in the code where you're awaiting an async function to resolve, you would add a check before setting state:

async componentDidUpdate(prevProps, prevState) {
    if (this.props.subject.length && prevProps.subject !== this.props.subject) {
        let result = await this.getGrades({
            student: this.props.id,
            subject: this.props.subject
        });
        !this.isCancelled && this.setState({
            subject: this.props.subject,
            grades: result
        });
    }
}

That will stop any state setting on unmounted/unmounting components

Dostie answered 19/5, 2018 at 22:8 Comment(0)
P
1

The accepted answer works, and is a valid workaround for the problem of calling asynchronous functions in the component rendering methods (getInitialState, componentWillMount, componentDidMount).

But a better practice would be to use state management helpers like Redux and Flux and a global store, this might avoid the problem of multiple setStates.

Postfree answered 2/8, 2018 at 12:10 Comment(4)
Thanks for the suggestion. Few people already recommended me to use Redux but I'm still in the process of learning and, as I haven't read about Redux, I'm still not sure when I should use it or not. I will definitely check it out after I learn the basics.Kirsch
I'm in a similar place, my suggestion comes from actually seeing the value in Redux while having a component without it using setState too many times. And it is a little bit weird to learn, i haven't applied it in practice myself, but it is indeed valuable. A small webapp with react might not need redux, if state is managed properly, it depends on the size of the component/webapp.Impugn
I don't think I completely agree with this assessment. The creator of Redux says that use Redux app state management when you really want to manage a piece of information that has impacts on your entire application. For everything else use regular async calls (i.e. fetch, axios etc.), regardless of the app's size. In short, use things that are a little less awkward. In my situation the data from the async call was not redux store worthy and wiring redux for this would've been an overkill, so the original answer's solution worked bestWileywilfong
This implies that React throws the error because it expects projects to contain a data store. I'm not so sure that's true.Crannog

© 2022 - 2024 — McMap. All rights reserved.