Search code examples
javascriptreactjsasynchronouses6-promise

How to Resolve Memory Leak and Cancel All Subscriptions Error in React App?


I have one warning in the console for my React app that mentions a memory leak with instructions. I'm not sure how to refactor my code to incorporate componentWillUnmount.

I do have useEffect() and JS promise code to pull and render data from a Firebase database but the error does not mention that component.

Please read the full warning error below here:

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 Nav (at Home.js:32)
        in div (at Home.js:30)
        in DashHome (created by Context.Consumer)

Nav.js code:

class Nav extends Component {
  state = {
    isSignedIn: true
  }

  componentDidMount = () => {
    firebase.auth().onAuthStateChanged(user => {
      this.setState({ isSignedIn: user })
    })
  }

  render() {
    return (
      <div className="side-nav">
        <Nav vertical>
          <NavItem>
            <NavLink href="#">Home</NavLink>
          </NavItem>

          {this.state.isSignedIn ? (
            <div>
              <Button onClick={() => firebase.auth().signOut()}>Sign-out</Button>
            </div>
          ) : (
              <Redirect to="/" />
            )}

        </Nav>
      </div>

    );
  }
}

export default Nav;

Home.js component (some code)

export default function Home() {
    const generateData = (value, length = 5) =>
        d3.range(length).map((item, index) => ({
            date: index,
            value: value === null || value === undefined ? Math.random() * 100 : value
        }));

    const [data, setData] = useState(generateData(0));
    const changeData = () => {
        setData(generateData());
    };

    useEffect(
        () => {
            setData(generateData());
        },
        [!data]
    );

I read about AbortController API in a tutorial but I'm not using Fetch in my app.

Thank you for taking a look! Please feel free to post any code refactoring ideas to resolve this.


Solution

  • The function returns an unsubscribe function when you call it.

    var unsubscribe = firebase.auth().onAuthStateChanged(function (user) {
            // handle it
        });
    

    You should call this function in componentWillUnmount like this.

    let unsubscribe;
    class Nav extends Component {
      state = {
        isSignedIn: true
      }
    
      componentDidMount = () => {
        unsubscribe = firebase.auth().onAuthStateChanged(user => {
          this.setState({
            isSignedIn: user
          })
        })
      }
      componentWillUnmount() {
        unsubscribe()
      }