Endless loop rendering component on ReactJs

Yonirt picture Yonirt · Nov 26, 2015 · Viewed 10.2k times · Source

I'm facing an infinite loop issue and I can't see what is triggering it. It seems to happen while rendering the components.

I have three components, organised like this :

TimelineComponent
   |--PostComponent
         |--UserPopover

TimelineComponenet:

React.createClass({
    mixins: [
        Reflux.listenTo(TimelineStore, 'onChange'),
    ],
    getInitialState: function() {
        return {
            posts: [],          
        }
    },
    componentWillMount: function(){
        Actions.getPostsTimeline();
    },
    render: function(){
        return (
            <div className="timeline">                  
                {this.renderPosts()}
            </div>
        );
    },
    renderPosts: function (){
        return this.state.posts.map(function(post){     
            return (
                <PostComponenet key={post.id} post={post} />
            );
        });
    },  
    onChange: function(event, posts) {
        this.setState({posts: posts});
    }
});

PostComponent:

React.createClass({
    ...
    render: function() {
        return (
            ...
           <UserPopover userId= {this.props.post.user_id}/>
            ...         
        );
    }
});

UserPopover:

module.exports = React.createClass({
   mixins: [
      Reflux.listenTo(UsersStore, 'onChange'),
   ],
   getInitialState: function() {
      return { 
        user: null
      };
   },
   componentWillMount: function(){
      Actions.getUser(this.props.userId);
   },
   render: function() {
      return (this.state.user? this.renderContent() : null);
   },
   renderContent: function(){
      console.log(i++);    
      return (
         <div>         
            <img src={this.state.user.thumbnail} />
            <span>{this.state.user.name}</span> 
            <span>{this.state.user.last_name}</span>
             ...
         </div>
      );
   },
   onChange: function() {
      this.setState({
         user: UsersStore.findUser(this.props.userId)     
      });
   }
});

Finally, there is also UsersStore**:

module.exports = Reflux.createStore({
    listenables: [Actions],
    users: [], 
    getUser: function(userId){      
        return Api.get(url/userId)
            .then(function(json){
                this.users.push(json);
                this.triggerChange();
        }.bind(this));
    },
    findUser: function(userId) {            
        var user = _.findWhere(this.users, {'id': userId});     
        if(user){
            return user;
        }else{
            this.getUser(userId);
            return [];
        }
    },
    triggerChange: function() {
        this.trigger('change', this.users); 
    }
});

Everything works properly except the UserPopover component.

For each PostComponent is rendering one UserPopOver which fetch the data in the willMount cycle.

The thing is, if you noticed I have this line of code console.log(i++); in the UserPopover component, that increments over and over

...
3820
3821
3822
3823
3824
3825
...

Clearl an infinite loop, but I really don't know where it comes from. If anyone could give me a hint I will be very gratefully.

PS: I already tried this approach in the UsersStore but then all the PostComponent have the same "user":

...
getUser: function(userId){      
    return Api.get(url/userId)
        .then(function(json){
            this.user = json;
            this.triggerChange();
    }.bind(this));
},
triggerChange: function() {
    this.trigger('change', this.user);  
}
...

And in the UserPopover

...
onChange: function(event, user) {
   this.setState({
      user: user     
   });
}
...

Answer

Zhang Chao picture Zhang Chao · Dec 15, 2015

Because that your posts is fetch async, I believe that when your UserPopover component execute it's componentWillMount, the props.userId is undefined, and then you call UsersStore.findUser(this.props.userId), In UserStore, the getUser is called because it can't find user in local storage.

NOTE that every time the getUser's ajax finished, it trigger. So the UserPopover component execute onChange function, and call UsersStore.findUser again. That's a endless loop.

Please add a console.log(this.props.userId) in the UserPopover's componentWillMount to find out if it is like what i said above. I actually not 100% sure it.

That is a problem that all UserPopover instance share the same UserStore, I think we should rethink the structure of these components and stores. But I haven't thought out the best way yet.