Search code examples
javascriptreactjsreact-state-managementreact-state

Update database from state variable when state changes


What i'm trying to do is update my database using a state variable when a save button is pressed. In the save handler a new value is added to the this.state.saved list and an updataDatabase function is called. The problem is that when the button is pressed setState does not update the state variable so therefore nothing is updated in the database. My question is: how can I make the setState update the component so that the database is updated? This is my code

class Articles extends Component{
    constructor(props){
        super(props);
        this.state = {
            check:false,
            saved:[]
        }

        this.handleSave = this.handleSave.bind(this)
    }

    async componentDidMount(){
        savedArticles = this.props.article.saved

        this.setState({
            check:this.props.article.check,
            saved:this.props.article.saved
    })
}

async updateDataBase(){
    let updates = {
        body:{
            userName:this.props.article.user,
            userEmail:this.props.article.email,
            userPhone:this.props.article.phone,
            savedArticles:this.state.saved,
            userInterests:this.props.article.interestList,

        }


    }
    console.log(updates)

    return await API.put(apiName,path,updates);
}

handleSave () {
    const urlList = [];
    let article = {};

    for(let i=0; i<this.state.saved.length; i++){
        urlList.push(this.state.saved[i].url)
    }
    if(!urlList.includes(this.props.article.url)){

        article =  {
            url:this.props.article.url,
            title:this.props.article.title,
            image:this.props.article.urlToImage,
            author:this.props.article.author,
            check:true,

        }
        savedArticles.push(article)
            this.setState({
                check: true,
                saved:[...this.state.saved,article]

            })


        this.updateDataBase();

    }
}

render()
{
    console.log(this.state.check)

    return (
        <div className="item">
            <div className="card">
                <img src={this.props.article.urlToImage} alt="No available image" width="100%" height="200px"></img>
                <div>

                    <h5 className="card-title">{this.props.article.title}</h5>
                    <p className="card-text">{this.props.article.author}</p>
                    <a href={this.props.article.url} target="_blank" id="articleLink" className="btn btn-primary"><FontAwesomeIcon icon="external-link-alt" /> See full article</a>

                    {this.state.check?(
                        <button disabled={true}  id="buttonsArticle" className="btn btn-primary"><FontAwesomeIcon icon="check" /> Saved</button>
                    ):(
                        <button onClick={this.handleSave.bind(this)} id="buttonsArticle" className="btn btn-primary"><FontAwesomeIcon icon="save" /> Save Article</button>
                    )}

                </div>
            </div>
            <div className="divider"></div>
        </div>

    )
}}

Solution

  • The problem there is that your code is running synchronously. You're setting the state and then calling this.updateDataBase(); but the state won't updated in that context. The state actually updating and the subsequent render occur after this. You can imagine it working like a queue where you call setState, it takes note that state will need to be updated but everything else immediately in the queue has to execute first.

    Take a look at this: https://codesandbox.io/s/32n06zlqop?fontsize=14 If you click the button, it immediately logs out the state where it is false and then logs it out again in componentDidUpdate and you can see that state has been set then.

    So you have two choices, you can call your update database function inside of a componentDidUpdate life cycle method. This function is called after each render which can be triggered by the state changing.

    Or, you can pass a second callback function argument to set state which be called after the state has been actually set. This would probably be the most elegant version:

    this.setState({
          check: true,
          saved:[...this.state.saved, article]
        }, this.updateDataBase)