0
votes

When I logout of my meteor app I get memory leak error. It seems to happen about 50% of the time and I can't figure out what I'm doing wrong here. Can someone please explain what's wrong with my method.

Error message

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.

App details

Metoer, React, React-Router V4

Path: LogoutButton.jsx

class LogoutButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      logoutRedirect: false
    };

    this.handleLogout = this.handleLogout.bind(this);
  }

  handleLogout = e => {
    e.preventDefault();

    Meteor.logout(err => {
      if (err) {
        console.log('err', err);
      } else {
        this.setState({ logoutRedirect: true });
      }
    });
  };

  render() {
    const logoutRedirect = this.state;

    if (logoutRedirect.logoutRedirect) {
      return <Redirect to="/" />;
    }

    return (
      <button
        type="button"
        className="btn btn-link dropdown-item text-dark"
        onClick={this.handleLogout}
      >
        <FontAwesomeIcon icon={faSignOutAlt} className="mr-2 text-dark" />
        Logout
      </button>
    );
  }
}
3

3 Answers

1
votes

So I'll break this down for you in understanding what the error is, why you are running into it and some possible ideas for a solution.

Your component LogoutButton is running into a scenario where it is unmounted (it is fully unrendered) but is attempting to trigger a state change within the component. This is generally just bad state flow generally stemming from doing some asynchronous action that takes time to do and then triggering a setState when there's nothing to, well, setState in.

Looking at your code it sounds like what you're trying to do is this:

  1. You have a <LogoutButton /> rendered onto the screen and when a user clicks it, they're supposed to be redirected.
  2. When a user clicks the button, it triggers handleLogout which explicitly calls Meteor.logout (I assume this is what is formally logging the user out from the session) and then proceed to call setState.

Most likely, Meteor.logout has some side-effect here to make it so your LogoutButton is no longer mounted or rendered. By the time your component gets to the step where it needs to call setState, your component is unmounted (maybe some parent component has decided to not show it anymore, etc).

I don't know enough about React-Router (which is what I assume the <Redirect /> is from but it looks like history.push or passing a prop called push to <Redirect /> is something worth considering. See this article.

0
votes

If anyone can shed more light on this I'd really appreciate it. The problem appears to be coming from how I handle my authenticated routes. I've solved the error with the code below, however, I'm not really sure why this works so I'm not able to give an explanation. The solution was to change the order of operations. First, update state to redirect route and then logout user.

class LogoutButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      logoutRedirect: false
    };

    this.handleLogout = this.handleLogout.bind(this);
  }

  componentWillUnmount() {
    document.removeEventListener('click', this.handleLogout);
  }

  handleLogout = e => {
    e.preventDefault();

    this.setState({ logoutRedirect: true });
    Meteor.logout(err => {
      if (err) {
        console.log('err', err);
      }
    });
  };

  render() {
    const logoutRedirect = this.state;

    if (logoutRedirect.logoutRedirect) {
      return <Redirect to="/" />;
    }

    return (
      <button
        type="button"
        className="btn btn-link dropdown-item text-dark"
        onClick={this.handleLogout}
      >
        <FontAwesomeIcon icon={faSignOutAlt} className="mr-2 text-dark" />
        Logout
      </button>
    );
  }
}
0
votes

step1 .meteor folder add this package : accounts-ui accounts-google accounts-password accounts-base std:accounts-ui useraccounts:bootstrap // step 2 : folder: imports/startup create folder name "accounts" add accounts-config.js and paste the code. change FlowRouter.go('/account/sign-in') to step 3 : Add function to your button. paste this let handleLogout = () => { AccountsTemplates.logout() }

const LogoutButton = () => {
   
  let handleLogout = event => {
    event.preventDefault()
    AccountsTemplates.logout()
  }

  return (
    <button
      type="button"
      className="btn btn-link dropdown-item text-dark"
      onClick={handleLogout}
    >
      <FontAwesomeIcon icon={faSignOutAlt} className="mr-2 text-dark" />
      Logout
    </button>
  )
}

Note: I'm using account base.

// Configure Accounts Templates default
AccountsTemplates.configure({
  enablePasswordChange: true,
  sendVerificationEmail: true,
  enforceEmailVerification: true,
  showForgotPasswordLink: true,
  negativeValidation: true,
  positiveValidation: true,
  negativeFeedback: true,
  positiveFeedback: true,
  // Proceed to main page as you log out..
  onLogoutHook: () => {
    <Redirect to="/" />
  }
})