0

Help, I cannot seem to figure out how to solve this problem:

array.map(object => {
   console.log(this.state.newArr) // [] didn't update despite setState
   if (this.state.newArr.length === 0) {
      axios.get('http://localhost/')
      .then(res => {
         this.setState(prevState => ({ newArr: [...prevState.newArr, res] })) // setState here
      })
   }
})

Edit: updated code implemented the answers, but still doesn't work atm.

5
  • We need more context. Where is this loop located? Commented May 8, 2020 at 16:23
  • Its in componentDidMount(), what I mean by loop is the map function. Commented May 8, 2020 at 16:24
  • componentDidMount only runs one time. It will not run again after your state is updated. Commented May 8, 2020 at 16:25
  • Yes I'm aware of that, but it should at least finish the map loop right? Commented May 8, 2020 at 16:26
  • It does, but setState is async, so it will no be using the updated value when it sets it. I'll answer with an example Commented May 8, 2020 at 16:27

2 Answers 2

1

Async/await pattern can be a solution. Never tried it in a map but it would be something like this :

array.map(async object => {
  if (this.state.newArr.length === 0) {
     const response = await axios.get('http://localhost/');
     await this.setStateAsync({ newArr: [...this.state.newArr, response] });
     // or without the setStateAsync 
     // this.setState(prevState => ({ newArr: [...prevState.newArr, response] })); 
  }
});

const setStateAsync = state => {
  return new Promise((resolve) => {
    this.setState(state, resolve)
  });
}
Sign up to request clarification or add additional context in comments.

4 Comments

@ErisSuryaputra Does the setStateAsync works properly ? If yes then i'll remove my previous update
Nope, I thought it worked before but unfortunately it still don't
There's no advantage to using the updater if you aren't going to use the prevState passed in.
@BrianThompson thank you i forgot to change that as well. It's edited :)
0

setState is async, meaning when setting state in a loop you will not yet have the updated state values. When you make state updates based on previous state values, you should use the updater form of setState.

Example of whats going wrong

class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      arr: []
    }
  }
  
  componentDidMount() {
    [1,2,3].forEach((i) => {
      // setState is async, 
      // so it has not updated by the next loop
      // meaning its the same every cycle
      console.log(this.state.arr)
      this.setState({ arr: [...this.state.arr, i] });
    })
  }
  
  render() {
    // Only the last enty is in the array
    console.log('final', this.state.arr)
    return (<div>test</div>);
  }
}

ReactDOM.render(<Example/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Example of the correct way

class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      arr: []
    }
  }
  
  componentDidMount() {
    [1,2,3].forEach((i) => {
      // Use the updater form so you are guaranteed
      // to have the most recent copy of state each update
      this.setState((prevState) => ({ arr: [...prevState.arr, i] }));
    })
  }
  
  render() {
   console.log('final: ', this.state.arr)
    return (<div>test</div>);
  }
}

ReactDOM.render(<Example/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Another feature of setState that may come in handy here is the callback option. You may pass a second argument to setState that is a function. That function will be called after the state is updated, so you can add logic there that is dependent on those updated values.

class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      arr: []
    }
  }
  
  componentDidMount() {
    [1,2,3].forEach((i) => {
      this.setState((prevState) => ({ arr: [...prevState.arr, i] }), () => {
        console.log('updated: ', this.state);
      });
      console.log('not updated: ', this.state);
    })
  }
  
  render() {
    return (<div>test</div>);
  }
}

ReactDOM.render(<Example/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

2 Comments

updater form is nice but I don't think it answer the question alone.The axios request isn't awaited and therefore the state update, even with the update form, won't be synchronous.
Oh you are right, after testing with the new prevState. This still hasn't solved the axios problem :( sorry

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.