Search code examples
reactjstypescriptsetstate

Want to set a state object within a for loop: React+Typescript


I have a state object which is basically an array of objects. I am doing a fetch call, and the array of objects returned by it should be set to the state object. But It I am unable to do the same. Any alternative? Where am I going wrong?

Help would be appreciated

 this.state = {
      accounts: [{firstname:"",lastname:"",age:"",id:""}],
 };

 async componentDidMount(){

   let useraccounts =  await this.fetchAccounts();
   if(useraccounts.length > 0)
     { 
       for(let i=0;i<useraccounts.length;i++)
        {
            account = {firstname:useraccounts[i].firstname,lastname:useraccounts[i].lastname,age:useraccounts[i].age,id:useraccounts[i].id}; 
            this.setState((prevState) => ({  accounts: [ ...prevState.accounts, account ]}));            
        }                   
     }  
 }

 fetchAccounts = async() => {

    //fetch API call which will return all users accounts

 }

Solution

  • You don't need to call setState for each account individually, just do a single call with all of the accounts:

    async componentDidMount(){
      try {
        let useraccounts =  await this.fetchAccounts();
        let newAccounts = useraccounts.map(({firstname, lastname, age, id}) => ({firstname, lastname, age, id}));
        this.setState(({accounts}) => ({accounts: [...accounts, ...newAccounts]}));
      } catch (e) {
        // Do something with the error
      }
    }
    

    That gets the accounts, creates a new array with just the relevant properties (what you were doing in your for loop), then calls setState to add the new accounts.

    Note that I'm doing destructuring in the parameter lists of the map callback and the setState callback to pick out only the parts of the objects they receive that I want. For instance, this:

    let newAccounts = useraccounts.map(({firstname, lastname, age, id}) => ({firstname, lastname, age, id}));
    

    is the same as this:

    let newAccounts = useraccounts.map(account => {
      return {
        firstname: account.firstname,
        lastname: account.lastname,
        age: account.age,
        id: account.id
      };
    });
    

    It's just a bit more concise.

    Of course, if you don't really need to copy the objects, you could just use the accounts you got from fetchAccounts directly:

    async componentDidMount(){
      try {
        let useraccounts =  await this.fetchAccounts();
        this.setState(({accounts}) => ({accounts: [...accounts, ...useraccounts]}));
      } catch (e) {
        // Do something with the error
      }
    }
    

    Some notes on your original code:

    • You're breaking one of the rules of promises by using an async function where nothing is going to handle the promise it returns: You need to handle any errors that occur, rather than ignoring them. That's why I added a try/catch.
    • If you're doing for(let i=0;i<useraccounts.length;i++), there's no need for if(useraccounts.length > 0) first. Your loop body won't run if there are no accounts.