Spectrum
Spectrum

Reputation: 340

Memory leak in my React app because of firebase and how to avoid it?

The app I'm working on displays a user dashboard on login with a sidebar for navigation. It uses firebase. I do most of my data fetch from firebase in my async componentDidMount() and store the data in my component state. It takes a couple of seconds to finish all fetches. But if the user decides to navigate to another screen before the fetch is complete, I get the

Can't call setState on unmounted component

warning (as expected). So I do some digging and find that

if(this.isMounted()) this.setState({updates:updates})

makes the warning go away, but then I also find that using isMounted is an antipattern.

The official documentation on this issue suggests tracking the mounted state ourselves by setting _isMounted=true in componentDidMount and then set it to false in the componentWillUnmount. The only way I see to achieve this would be through a variable in component state. Turns out, setState doesn't work in componentWillUnmount. [Issue 1] (I tried calling an external function from componentWillUnmount which in turn sets the state variable. Didn't work.)

The documentation suggests another way, to use cancellable promises. But I'm clueless about how to achieve that with await firebase calls. I also couldn't find any way to stop firebase calls mid-track. [Issue 2]

So now I'm stuck with the warning and data leaks.

a. How do I resolve this problem?
b. Is this something I need to take seriously?

Upvotes: 4

Views: 1305

Answers (1)

Tholle
Tholle

Reputation: 112777

It's good practice to check if the component is still mounted when a request completes, if there is a risk of the component unmounting.

You don't need to put _isMounted in your component state since it will not be used for rendering. You can put it directly on the component instance instead.

Example

class MyComponent extends React.Component {
  state = { data: [] };

  componentDidMount() {
    this._isMounted = true;

    fetch("/example")
      .then(res => res.json())
      .then(res => {
        if (this._isMounted) {
          this.setState({ data: res.data });
        }
      });
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  render() {
    // ...
  }
}

Upvotes: 3

Related Questions