1

I have a parent component named UsersTable(it's a child of some other component and has users and rolesas its props). The getRoles() function is getting all the roles for a user using an ajax request. The result is returned to render() and stores in allrolesvariable. The problem is since Ajax is asynchronous, I'm not always getting the values I need in allroles variable. It is sometimes empty or returns the same value for all users. I guess I should somehow use componentDidMount and componentWillReceiveProps according to descriptions. But I couldn't figure it out. Also, I think allroles should be a state but doing that put the code in an infinite loop, and the browser keeps calling the ajax request forever!

Here is the parent component code:

export const UsersTable = React.createClass({
    getRoles(){
        var oneRole = "";
        this.props.users.forEach(function(user){
            server.getUserRoles(user.id,          
                (results) => {
                    this.oneRole =results['hits']['hits']
                    notifications.success("Get was successful ");
                },
                () => {
                    notifications.danger("get failed ");
                });  
            }.bind(this));
        return this.oneRole;
    },

    render() {
        var rows = [];
        var allroles = this.getRoles()
        this.props.users.map(function(user) {
            rows.push( <UserRow userID={user.id} 
                                userEmail={user.email} 
                                userRoles={allroles} 
                                roles={this.props.roles} />); 
            }.bind(this)); 
        return (
            <table className="table">
                <thead>
                    <tr>
                        <th>Email Address</th>
                        <th>Role</th>
                        <th>Edit</th>
                    </tr>
                </thead>
                <tbody>{rows}</tbody>
            </table>
        );
    }    
});

And here is the child component code:

export const UserRow = React.createClass({
    var showRoles = this.props.userRoles.map((role) => <div> {role.description || ''}</div>);
    render(){
        return (
            <tr>
                <td>{this.props.userEmail}</td>
                <td>{showRoles}</td>
            </tr>
        );
    }
});
petergil
  • 180
  • 1
  • 6
  • you dont use well the ajax asyncronous concept. http://stackoverflow.com/questions/4559032/easy-to-understand-definition-of-asynchronous-event – Álvaro Touzón Feb 22 '17 at 09:26

2 Answers2

0

You are correct in saying you should use the life cycle methods.

As a general rule, use componentDidMount if you just need to populate the data once at the beginning.

Use componentWillReceiveProps if it has to collect more data (based on props changing).

As a sidenote, for your particular example, you seem to be calling the backend multiple times via a loop. Its impossible to tell from the question, but if you are using a promise library you can push all those promises into an array, and do a Promise.all().then(/* update state */)

Chris
  • 54,599
  • 30
  • 149
  • 186
  • I'm not sure how to do it. for example assume I have a componentDidMount function, in its body I should call getRoles(), right? But then how to pass the user to it ? Also, I don't know about Promises :( Is there any other way to fix it? Should I better move it from render() function to a new function? I think I've read somewhere that states shouldn't get update in render(). Maybe that's the reason.... – petergil Feb 22 '17 at 09:56
  • i'll put togther a quick codepen. what request library are you using? axios? – Chris Feb 22 '17 at 10:56
0

First things first, to put that in a state without making it an infinite loop, you need a constructor method in which you call the super() method and then you are able to define the state. So you need something like this:

constructor() {
    super();
    this.state = {
       allroles: null
    }
}

After that, as you correctly pointed out, you need the life cycle hook: componentDidMount(),

You need the first one to populate your state. So to call it, should look something like this:

componentDidMount() {
   this.setState({allroles: this.getRoles()})
}

Now if you need to fetch new updates, you must know whether this updates will be triggered as a user's interaction consequence (e.g. they add a new entry on the table) or time-based update (e.g. using a interval that calls this.getRole() every certain amount of time). Either way, you would need to use this.setState({allroles: this.getRoles()}) again.

In this case, you don't need componentWillReceiveProps() because this component is managing its own state without receiving props. If you would have received the roles as props, you would have need componentWillReceiveProps()

And last, to answer why you had an infinite loop is because this.setState() triggers a re-rendering of the component. That means that if you call this.setState() in the render() method, you will

  1. render()
  2. re-render triggered
  3. render()
  4. re-render triggered ...

That's why you can't call directly this.setState() in your render() method and rather call it either on a event or on certain lifecycle hooks (the ones that are called BEFORE the rendering happens or are considered to be the INITIAL render)

juanecabellob
  • 458
  • 3
  • 14