0

I am new to react. I am trying to fetch data from remote server using _fetchUsers() dynamically. The state apiUrl should be changed when clicking different nav tabs, although not showing here. The problems, I find that, the log before $.ajax function shows empty string of this.state.apiurl. If I hardcode the url on ajax's url property, the ajax can return right response. Can someone explain why I fail to setState for the apiurl, is the reason related to componentWillMount()

export default class RegiteredUserTable extends Component {
    constructor() {
        super();
        this.state = {
            registeredUsers:[],
            tableProperties:[],
            apiUrl:''
        }
    }
    _fetchUsers() {
        console.log("this.props", this.props);
        if(this.props.match.params.tableId === "buyer-table"){
            this.setState({apiUrl:'/buyers/list-buyers'});
        } else if (this.props.match.params.tableId === "seller-table") {
            this.setState({apiUrl:'/sellers/list-sellers/'});
        }
        console.log('this.state.apiUrl',this.state.apiUrl);    
        $.ajax({
            method: 'GET',
            url: `${this.state.apiUrl}`,
            success: (res) => {
                console.log('res',res);
                let registeredUsers = res.data;
                this.setState({registeredUsers});
            }
        });
    }

    _getTableProperty() {
        //property array
        console.log('this.state.registeredUsers',this.state.registeredUsers);
        const tableProperties = this.state.registeredUsers[0].keys;
        this.setState({tableProperties});
        return tableProperties.map(tableProperty => {
            return  (
                        <th>{tableProperty}</th>
                    )
        })
    }

    _getUsers() {
        return this.state.registeredUsers.map(registeredUser => {
                return (
                    <tr>
                        <td>{registeredUser.id}</td>
                        <td>{registeredUser.first_name}</td>
                        <td>{registeredUser.last_name}</td>
                    </tr>
                );
            }
        )
    }
    componentWillMount() {
        this._fetchUsers();
    }
    render() {
        return(
            <div className="container">
                <div className="row">
                    <div className="col-xs-12">
                    <div className="table-responsive">
                        <table className="table table-bordered table-hover">
                        <caption className="text-center">Agents' data</caption>
                        <thead>
                            <tr>
                                {this._getTableProperty()}
                            </tr>
                        </thead>
                        <tbody>
                            {this._getUsers()}
                        </tbody>
                        <tfoot>
                            <tr>
                            <td colSpan="5" className="text-center">Data retrieved from <a href="http://www.infoplease.com/ipa/A0855611.html" target="_blank">infoplease</a> and <a href="http://www.worldometers.info/world-population/population-by-country/" target="_blank">worldometers</a>.</td>
                            </tr>
                        </tfoot>
                        </table>
                    </div>
                    </div>
                </div>
            </div>

        );
    }
}
Wayne Li
  • 411
  • 1
  • 6
  • 16
  • check the reason here setState is async: http://stackoverflow.com/questions/42593202/why-calling-setstate-method-doesnt-mutate-the-state-immediately – Mayank Shukla May 11 '17 at 07:36

1 Answers1

0

SetState is async, so do the api call once setState set the apiUrl in state variable, use a callback method and do the api call inside that.

Write the _fetchUsers like this:

_fetchUsers() {
    let tableId = this.props.match.params.tableId;

    this.setState({
        apiUrl: tableId === "buyer-table" ? '/buyers/list-buyers' : '/sellers/list-sellers/'
    }, () => {
         $.ajax({
            method: 'GET',
            url: `${this.state.apiUrl}`,
            success: (res) => {
                console.log('res',res);
                let registeredUsers = res.data;
                this.setState({registeredUsers});
            }
        });
    });       
}

Or better way will be just store the apiUrl in a variable and do setState once you get the response, by that way you don't need to do setState twice and don't need to depend on first setState.

Like this:

_fetchUsers() {
    let tableId = this.props.match.params.tableId;
    let url = tableId === "buyer-table" ? '/buyers/list-buyers' : '/sellers/list-sellers/';
    $.ajax({
        method: 'GET',
        url: url,
        success: (res) => {
            console.log('res',res);
            let registeredUsers = res.data;
            this.setState({registeredUsers, apiUrl: url});
        }
    });       
}
Mayank Shukla
  • 100,735
  • 18
  • 158
  • 142
  • You should use `const`, not `let`, if the value won't change. – Sulthan May 11 '17 at 08:13
  • Hi, I used your method and did get the right data for state of apiUrl shown in my log. However, I found another problem. The log for the state of registeredUsers inside the function _getTableProperty() is still empty. I thought I have invoked the _fetchUsers() to set the registeredUsers state inside the function componentWillMount() to make it ready before render, but in fact, according to my log info, it fails to behave like what I think. Why is that? – Wayne Li May 11 '17 at 09:20
  • I found the answer, _getTableProperty() is invoked twice in this case. First time is empty(initial one), second time is invoked after componentWillMount() completed, which gets the data. If I want to update the url depends on different url params, I should invoked _fetchUsers inside componentWillReceiveProps() method again, reference to http://stackoverflow.com/questions/38915066/on-url-change-i-want-to-re-render-my-component-how-should-i-do-that – Wayne Li May 12 '17 at 01:51