React js - component not reactive upon logout


#1

I have a component where need user authentication, if the user not logged in, there will be a login form displayed. Otherwise display the content.

Here is what I did:
The main layout for the component.

export default class MainLayout extends TrackerReact(React.Component) {
    	constructor(props) {
    	    super(props);
    	    this.state = {
    	      isLoggedIn : User.isLoggedIn()
    	    };
    	}
    	logout(e){
   
    		Meteor.logout( (err) =>{
    			if(err){
    				Bert.alert(error['reason'], 'danger', 'fixed-top', 'fa-frown-o' );
    			}else{    			
    				 this.setState({isLoggedIn: !User.isLoggedIn()});   		    			
    			}
    		});    		
    	}

    	render() {
    		if(User.isLoggedIn()){

    		return (
    			<div>
    				   <SiteHeader logout={this.logout} menu='LoginMenu'/>
    	
    				    <div className="container-fluid top-offset">

    				        {this.props.content}
    				    </div> 			

    			</div>)
    		}else{
    			return (<Login />)
    		}
    	}
  } 

And this is the SiteHeader component:

export default class SiteHeader extends React.Component {   
        logout(e) {
            e.preventDefault();
            this.props.logout(e)
        }

        render(){
            if(this.props.menu == 'LoginMenu'){
                menu =(
                        <ul className="nav navbar-nav navbar-right">
                        <li><a href="/" >Home</a></li>
                        <li><a href="/dashboard">My Project</a></li>
                        <li><a href="#" onClick={this.logout.bind(this)}>Logout</a></li>
                        </ul>   

                    );
            
            }else{

                menu =(
                        <ul className="nav navbar-nav navbar-right">
                        <li><a href="/" >Home</a></li>
                        <li><a href="/login">Login</a></li>
                        </ul>   

                    );
                    
            }
            return ( {menu} )
        }
}

SiteHeader.propTypes = {
        logout :   React.PropTypes.func

}; 

So when I logged-in, I can see the dom updated to the authorized content , but when I logout, the dom is not updating to a login form, however the user is logged out properly.

When I click on the logout link, this is what i get from the console:

xception in delivering result of invoking ‘logout’: TypeError: _this2.setState is not a function
at http://localhost:3000/app/app.js?hash=c420dcaf4d58b9f867c19634b52cae3fae730321:510:13
at http://localhost:3000/packages/accounts-base.js?hash=9a2df45ebeba9d14f693547bc91555a09feda78e:201:23
at null._callback (http://localhost:3000/packages/meteor.js?hash=ae8b8affa9680bf9720bd8f7fa112f13a62f71c3:1105:22)
at _.extend._maybeInvokeCallback (http://localhost:3000/packages/ddp-client.js?hash=b5f1b97df6634673c68f37914ae9f4c3231c438e:3541:12)
at _.extend.receiveResult (http://localhost:3000/packages/ddp-client.js?hash=b5f1b97df6634673c68f37914ae9f4c3231c438e:3561:10)
at .extend.livedata_result (http://localhost:3000/packages/ddp-client.js?hash=b5f1b97df6634673c68f37914ae9f4c3231c438e:4681:9)
at onMessage (http://localhost:3000/packages/ddp-client.js?hash=b5f1b97df6634673c68f37914ae9f4c3231c438e:3369:12)
at http://localhost:3000/packages/ddp-client.js?hash=b5f1b97df6634673c68f37914ae9f4c3231c438e:2736:11
at Array.forEach (native)
at Function.
.each.
.forEach (http://localhost:3000/packages/underscore.js?hash=27b3d669b418de8577518760446467e6ff429b1e:149:11)
debug.js:41 Exception from Tracker recompute function:
debug.js:41 TypeError: Cannot read property ‘call’ of undefined
at Dashborad.TrackerReactComponent._this.constructor.componentWillUnmount (main.js:47)
at ReactCompositeComponentMixin.unmountComponent (ReactCompositeComponent.js:316)
at Object.ReactReconciler.unmountComponent (ReactReconciler.js:65)
at Object.ReactChildReconciler.unmountChildren (ReactChildReconciler.js:117)
at ReactDOMComponent.ReactMultiChild.Mixin.unmountChildren (ReactMultiChild.js:325)
at ReactDOMComponent.Mixin.unmountComponent (ReactDOMComponent.js:883)
at Object.ReactReconciler.unmountComponent (ReactReconciler.js:65)
at Object.ReactChildReconciler.unmountChildren (ReactChildReconciler.js:117)
at ReactDOMComponent.ReactMultiChild.Mixin.unmountChildren (ReactMultiChild.js:325)
at ReactDOMComponent.Mixin.unmountComponent (ReactDOMComponent.js:883)

Is this the meteor that causing this problem or it just the react js?


#2

So first I would say look at whatever function you are passing into <SiteHeader logout={ someFunc } /> because that is the what is causing this error if I had to guess…


#3

So, it’s actually really simple, your problem is right here:

The solution:

It needs to be instead:

<SiteHeader logout={this.logout.bind(this)}... OR: <SiteHeader logout={() => {this.logout();}} ...>

The really long winded explanation:

The reason you are getting your setState is not a function is because react es6 variant doesn’t autobind.

Since javascript by default uses early bind function pointers (computer science nerd alert), this.logout is a pointer to the function itself on the prototype of this but without the object reference. A late-bind function pointer on the other hand (which javascript doesn’t have) would actually have held the reference to this and remember that you wanted function logout, and when called while evaluate this in the scope that was there previously and call logout on the result of that evaluation at function call time. Javascript does however have an explicit bind which allows to bind an object scope to a function, returning a new function which does little more than pretend to be late bound.

Basically

var logout = this.logout;
// does not call the object
logout();

// the actual call on the object.
this.logout();

logout = this.logout.bind(this);
// as it turns out, also the actual call on the object.
logout();

A second level of confusion could have occurred because your stack trace references _this2 which doesn’t make a whole lot of sense at first glance. However, it is how babel transpiles arrow functions for ES5 browsers.

logout(e){
  Meteor.logout( (err) =>{
    if(err){
      Bert.alert(error['reason'], 'danger', 'fixed-top', 'fa-frown-o' );
    }else{
      this.setState({isLoggedIn: !User.isLoggedIn()});
    }
  });
}

gets turned into

logout(e){
  var _this2 = this;
  Meteor.logout( function(err) {
    if(err){
      Bert.alert(error['reason'], 'danger', 'fixed-top', 'fa-frown-o' );
    }else{
      _this2.setState({isLoggedIn: !User.isLoggedIn()});
    }
  });
}

for which the line in the else statement is where you are getting your exception. At that point, _this2 will be window not your component, because of the aforementioned binding problem.


#4

I tried with [quote=“lassombra, post:3, topic:22077”]
<SiteHeader logout={this.logout.bind(this)}… OR: <SiteHeader logout={() => {this.logout();}} …>
[/quote]

But it still not working, now It give me these exception:

Exception in delivering result of invoking ‘logout’: TypeError: Cannot read property ‘call’ of undefined
at Dashborad.TrackerReactComponent._this.constructor.componentWillUnmount (http://localhost:3000/packages/ultimatejs_tracker-react.js?hash=18a16ff1bcf59e6f7ecb492786a478a079e51ea2:114:38)
at ReactCompositeComponentMixin.unmountComponent (http://localhost:3000/packages/modules.js?hash=fc0690fd2e52b9b08b2901e222264042c995bab7:13520:14)
at Object.ReactReconciler.unmountComponent (http://localhost:3000/packages/modules.js?hash=fc0690fd2e52b9b08b2901e222264042c995bab7:7127:22)
at Object.ReactChildReconciler.unmountChildren (http://localhost:3000/packages/modules.js?hash=fc0690fd2e52b9b08b2901e222264042c995bab7:13066:25)
at ReactDOMComponent.ReactMultiChild.Mixin.unmountChildren (http://localhost:3000/packages/modules.js?hash=fc0690fd2e52b9b08b2901e222264042c995bab7:12801:28)
at ReactDOMComponent.Mixin.unmountComponent (http://localhost:3000/packages/modules.js?hash=fc0690fd2e52b9b08b2901e222264042c995bab7:10109:10)
at Object.ReactReconciler.unmountComponent (http://localhost:3000/packages/modules.js?hash=fc0690fd2e52b9b08b2901e222264042c995bab7:7127:22)
at Object.ReactChildReconciler.unmountChildren (http://localhost:3000/packages/modules.js?hash=fc0690fd2e52b9b08b2901e222264042c995bab7:13066:25)
at ReactDOMComponent.ReactMultiChild.Mixin.unmountChildren (http://localhost:3000/packages/modules.js?hash=fc0690fd2e52b9b08b2901e222264042c995bab7:12801:28)
at ReactDOMComponent.Mixin.unmountComponent (http://localhost:3000/packages/modules.js?


#5

this stack trace is all in code outside of your component. My guess is that one of your callbacks is triggering after the component is unmounted.

I see at least 3 different references to things outside of what you have posted. Do you possibly have a recreate repo (or could post one) so I could pull it in and take a closer look?


#6

Finally found the problem. Turns out is the child component (from the this.props.content) using the Tracker React, which caused the problem. Now I move the Tracker react to the parent component and everything is working correctly now.

Anyway, thanks for your help. Your answer really inspired me debugging the problem. :slight_smile:


#7

For anyone else still searching, would like to add on that I also ran into this issue, where Meteor.user() was not reactive after calling Meteor.logout(). I resolved it by calling this.forceUpdate() in the logout callback, because I noticed that Meteor.user() would react if I triggered an update elsewhere in the app.


#8

Hi,
I am facing the problem now solved my problem.tanks for sharing the article.
Regards,
John
React js Developer http://www.courseing.com/learn/reactjs-online-training