17
votes

I'm implementing firebase auth in a react web app with react-router.

A user signs in (at /signin) with either Facebook or Google using the popup sign in, then if successful I route to the main app (/). In the main app component I listen for an auth state change:

  componentWillMount() {
    this.authListener = this.authListener.bind(this);
    this.authListener();
  }

authListener listens for the auth change:

authListener() {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        console.log('user changed..', user);
        this.setState({
          User: {
            displayName: user.displayName
          }
        });
      } else {
        // No user is signed in.
        browserHistory.push('/signin');
      }
    });
  }

Everything works fine, except when I sign out (and go back to /signin) and sign in again using facebook or google. Then I get an error saying:

Warning: setState(...): Can only update a mounted or mounting component.

I suspect that the onAuthStateChanged listener from the now unmounted previous logged in state app is still running.

Is there a way to remove the onAuthStateChanged listener when the App component unmounts?

4

4 Answers

28
votes

Any listeners that you have set up will also need to be torn down.

Your suspicions are very much on-spot.

You should use the componentWillUnmount lifecycle method to remove any leftover listeners that might pollute your app.

To clear up the listener, here's the relevant code:

Inside your authListener function you need to save a reference to the listener inside your component (it is returned to you as a result of calling firebase.auth().onAuthStateChanged). It will be a hook that will un-reference the listener and remove it.

So instead of just calling it, save the returned value as such

this.fireBaseListener = firebase.auth().onAuthStateChanged ...

And when your component un-mounts, use this code:

componentWillUnmount() {
   this.fireBaseListener && this.fireBaseListener();
   this.authListener = undefined;
}
28
votes

I know I am late to the game, but here's a hooks based solution:

React.useEffect(() => {
    const unsubscribe = firebase.auth().onAuthStateChanged((user) => { // detaching the listener
        if (user) {
            // ...your code to handle authenticated users. 
        } else {
            // No user is signed in...code to handle unauthenticated users. 
        }
    });
    return () => unsubscribe(); // unsubscribing from the listener when the component is unmounting. 
}, []);
2
votes

@Justin because onAuthStateChanged returns function so you can use it to clear the listener... this.fireBaseListener = firebase.auth().onAuthStateChanged

docs: https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged

Returns non-null firebase.Promise containing non-null Array of string

0
votes

Instead of checking onAuthStateChanged() function inside componentDidMount() you can check subscription's like this.

componentWillMount() {
    //following line will help you to setState() but not required
    let set =  this  

    //this is our trick
    this.unsubscribe  = firebase.auth().onAuthStateChanged(user => {
        if (!user) {
            //navigate to guest stack
            //actually im using react-navigation
            set.props.navigation.navigate('Guest'); 
        } else {
            //set current user to state 
            set.setState({
                user: user,
                loading: false
            })
        }
    });
}

//What you have to do next is unsubscribe ;) 
componentWillUnmount() {
    this.unsubscribe();
}