简体   繁体   中英

React: initial render fires before state is set (via ajax)

I've set things up so the HomePage component renders UserShow for the current logged in user. For example, if a user with an ID of 2 is logged in and visits the HomePage page, it will render their UserShow .

The "normal" UserShow works correctly. For example if you type in /users/18, it will properly render. However it's not working when HomePage renders it.

I'm new to React (especially its lifecycle methods), so my debugging has been to throw alerts in at various steps. I'd say the most important findings to share are:

  1. currentUserID( ) is functioning and returns the correct ID
  2. Hard-coding the value of state.userID within componentDidMount causes things to work correctly

These two points lead me to believe that Render is being called before it can update state.userID with its (correct) return value. Even more specific is that it's rendering before the .success portion of the this.currentUserID() ajax call returns. If this is so, what's the best way to go about not doing an initial render until an ajax call like this completes?

My code is in a state of spaghetti - it's my first time doing front-end routing with JavaScript. I'm also managing sessions via using the user's email as the token in localStorage - I'm new to sessions in JS as well. Please bear with me.

HomePage component:

var HomePage = React.createClass({

getInitialState: function(){
    return{
        didFetchData: false,
        userID: null,
    }
},

componentWillMount: function(){
    newState = this.currentUserID()
    this.setState({userID: newState}) 
    // this.setState({userID: 2})   //hard-coding the value works
},

currentUserID: function(){
    if(App.checkLoggedIn()){
        var email = this.currentUserEmail()
        this.fetchUserID(email)
    }else{
        alert('theres not a logged in user')
    }
},

currentUserEmail: function(){
    return localStorage.getItem('email')
},

fetchUserID: function(email){ //queries a Rails DB using the user's email to return their ID
    $.ajax({
        type: "GET",
        url: "/users/email",
        data: {email: email},
        dataType: 'json',
        success: function(data){
            this.setState({didFetchData: 'true', userID: data.user_id})
        }.bind(this),
        error: function(data){
            alert('error! couldnt fetch user id')
        }
    })
},

render: function(){
    userID = this.state.userID
    return(
        <div>
            <UserShow params={{id: userID}} />
        </div>
    )
}
})

UserShow component:

var UserShow = React.createClass({

getInitialState: function(){
    return{
        didFetchData: false,
        userName: [],
        userItems: [],
        headerImage: "../users.png"
    }
},

componentDidMount: function(){
    this.fetchData()
},

fetchData: function(){  
    var params = this.props.params.id
    $.ajax({
        type: "GET",
        url: "/users/" + params,
        data: "data",
        dataType: 'json',
        success: function(data){
            this.setState({didFetchData: 'true', userName: data.user_name, userItems: data.items, headerImage: data.photo_url})
        }.bind(this),
        error: function(data){
            alert('error! couldnt load user into user show')
        }
    })
},

render: function(){
    var userItem = this.state.userItems.map(function(item){
        return <UserItemCard name={item.name} key={item.id} id={item.id} description={item.description} photo_url={item.photo_url} />
    })
    return(
        <div>
            <Header img_src={this.state.headerImage} />

            <section className="body-wrapper">
                {userItem}              
            </section>
        </div>
    )
}
})

So what you want to do is to avoid rendering anything until your ajax-request returns your result.

You can do a check in the render method if the state is how you want it. If it's not, then return null, or a loader or some other markup. When the componentDidMount then sets the state, it will trigger a re-render, since the userID then is set, it will return the userShow component

Example:

render(){
  if(this.state.userID === null){
     return null; //Or some other replacement component or markup
  }

  return (
    <div>
        <UserShow params={{id: userID}} />
    </div>
  );

}

Fetching the data in the userShow component could be done like this:

componentWillReceiveProps(nextProps){
    //fetch data her, you'll find your prop params in nextProps.params
}

You can also avoid doing this here, by kicking the data-fetch in the render-method.

Your initial data set what your doing in componentWillMount will not help to set data because you want to fetch data from ajax. Debug fetchUserID and currentUserID function whether your getting correct email from localstorage and user id from server. Others is fine.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM